READ ME

SUMMARY
This R code is used to estimate the relationship between oxygen consumption (MO2) and ambient oxygen partial pressure (PO2) in the Common galaxias (Galaxias maculatus). It is also used to estimate the critical partial pressure of oxygen for aerobic metabolism (Pcrit), which is commonly understood as the threshold below which oxygen consumption rate can no longer be sustained. The associated article is “The role of osmorespiratory compromise in hypoxia tolerance of the purportedly oxyconforming teleost Galaxias maculatus”.

AIM The article aims to test whether Galaxias maculatus can maintain oxygen consumption (MO2) as ambient PO2 falls, and if so, at what level it reaches critical partial pressure of oxygen for aerobic metabolism (Pcrit).

AUTHORS
To be added

AFFILIATIONS
To be added

AIM
To be added

Knit settings

These are the settings for the html output. We will use this to make out index file on Git

#kniter seetting
knitr::opts_chunk$set(
message = FALSE,
warning = FALSE, # no warnings
cache = TRUE,# Cacheing to save time when kniting
tidy = TRUE
)

Required packages

These are the R packages required for this script. You will need to install a package called pacman to run the p_load function.

# this installs and load packages
# need to install pacman
pacman::p_load("ggplot2", 
               "ggthemes", 
               "ggfortify", 
               "gtExtras", 
               "igraph",
               "dagitty",
               "ggdag",
               "ggridges",
               "gghalves",
               "ggExtra",
               "gridExtra",
               "corrplot",
               "RColorBrewer", 
               "gt", 
               "gtsummary",
               "grid",
               "plotly", # data visualisation
               
                "tidyverse", 
               "janitor", 
               "readxl", 
               "broom.mixed", 
               "data.table", 
               "devtools",
               "hms",
               "round_hms", # data tidy
               
               "marginaleffects", 
               "brms", 
               "rstan", 
               "performance", 
               "emmeans", 
               "tidybayes", 
               "vegan",
               "betareg",
               "lme4", 
               "car", 
               "lmerTest",
               "qqplotr",
               "respirometry",
               "mclust",
               "furrr",
               # modelling 
              
               
               "datawizard", 
               "SRS" # data manipulation 
                       )

Functions (custom)

Here are some custom function used within this script.

bayes_incremental_regression_by_id: A custom function to build Bayesian incremental regressions in parallel

Note: add case where id = NULL

# Instead we could also use a distributional regression approach, by
# specifically modelling the variance by DO (e.g. sigma ~ DO). Weighting may
# not be required in this case, I don't think higher density of vaules in a
# given space will effect Bayesian estimates like it does in frequentist
# models. See discourse https://discourse.mc-stan.org/t/weights-in-brm/4278
# <br>

bayes_incremental_regression_by_id <- function(id_i, id_name, data, predictor, response,
    save_models, mod_output_wd) {
    # Initiate an empty list to store models
    models <- list()

    # If id_i and id_name is empty add

    # Filter data for the current ID
    df_i <- data %>%
        dplyr::filter(!!rlang::sym(id_name) == id_i)

    # Dynamically create formulas
    formula_lm_0 <- reformulate("1", response)
    formula_lm_1 <- reformulate(predictor, response)
    formula_lm_2 <- reformulate(sprintf("poly(%s, 2)", predictor), response)
    formula_lm_3 <- reformulate(sprintf("poly(%s, 3)", predictor), response)

    # Fit and store models in the list
    models[[paste0(id_i, "_lm_0")]] <- brm(bf(formula_lm_0, family = gaussian()),
        data = df_i, cores = 4, seed = 143019, save_pars = save_pars(all = save_models),
        sample_prior = FALSE, silent = TRUE, file = paste0(mod_output_wd, "/", id_i,
            "_lm_0"))

    models[[paste0(id_i, "_lm_1")]] <- brm(bf(formula_lm_1, family = gaussian()),
        data = df_i, cores = 4, seed = 143019, save_pars = save_pars(all = save_models),
        sample_prior = FALSE, silent = TRUE, file = paste0(mod_output_wd, "/", id_i,
            "_lm_1"))

    models[[paste0(id_i, "_lm_2")]] <- brm(bf(formula_lm_2, family = gaussian()),
        data = df_i, cores = 4, seed = 143019, save_pars = save_pars(all = save_models),
        sample_prior = FALSE, silent = TRUE, file = paste0(mod_output_wd, "/", id_i,
            "_lm_2"))

    models[[paste0(id_i, "_lm_3")]] <- brm(bf(formula_lm_3, family = gaussian()),
        data = df_i, cores = 4, seed = 143019, save_pars = save_pars(all = save_models),
        sample_prior = FALSE, silent = TRUE, file = paste0(mod_output_wd, "/", id_i,
            "_lm_3"))

    # Return the list of models for the current ID
    return(models)
}

load_rds: A custom function to load all rds models in a directory and store in a list

load_rds <- function(model_dw) {
    # List all .rds files in the directory
    model_file_list <- list.files(path = model_dw, pattern = "\\.rds$", full.names = TRUE)

    # Initialise an empty list to store models
    model_store_list <- list()

    # Iterate through each file and load the RDS
    for (mod_i in model_file_list) {
        mod <- readRDS(file = mod_i)  # Read the RDS file
        model_name <- tools::file_path_sans_ext(basename(mod_i))  # Extract the file name without extension
        model_store_list[[model_name]] <- mod  # Store it in the list
    }

    # Return the list of models
    return(model_store_list)
}

incremental_regression_bayes_fits: A custom function for pulling model fits, loo and r2

# Define Function to Process the data for each ID
incremental_regression_bayes_fits <- function(models) {
  
  loo_results_list <- list()
  
  # Iterate over the names of the models
  for (mod_name in names(models)) {
    # Extract the model
    mod_i <- models[[mod_name]]
    
    # Compute LOO results
    mod_loo_results_i <- loo::loo(mod_i)
    
    # Extract relevant LOO metrics
    elpd_loo_i <- mod_loo_results_i$elpd_loo
    p_loo_i <- mod_loo_results_i$p_loo
    looic_i <- mod_loo_results_i$looic
    
    # Create a data frame with metrics
    df_i <- data.frame(
      elpd_loo = elpd_loo_i,
      p_loo = p_loo_i,
      looic = looic_i,
      model = mod_name
    )
    
    est_i <- tidy(mod_i, effects = "fixed", conf.int = TRUE) %>% 
      dplyr::select(term, estimate, conf.low, conf.high) %>% 
      tidyr::pivot_wider(
        names_from = term,                # Use `term` as column names
        values_from = c(estimate, conf.low, conf.high),  # Values to pivot
        names_sep = "_"                   # Add a separator to column names
      )
    
    df_i <- cbind(df_i, est_i)
    
    # Store the data frame in the list
    loo_results_list[[mod_name]] <- df_i
  }
  
  # Combind 
  loo_results_combined <- bind_rows(loo_results_list)
  
  # Get R2 
  r2_results <- map_dfr(models, ~ as.data.frame(bayes_R2(.x)), .id = "model") %>%
    tibble::remove_rownames()
  
  # Combind R2 and loo results 
  model_fit_df <- dplyr::full_join(loo_results_combined, r2_results, by = "model") %>% 
    dplyr::select(model, everything()) %>% 
    dplyr::rename(r2 = Estimate,
                  r2_error = Est.Error,
                  r2_q2.5 = Q2.5,
                  r2_q97.5 = Q97.5) %>% 
    dplyr::mutate(id = sub("_(lm_\\d+)$", "", model),
                  model_type = sub("^.*_(lm_\\d+)$", "\\1", model))
  
  return(model_fit_df)
}

bayes_mod_predictions: This function extracts the predicted values from a list of models and combinds it with the orgnial data file used for the model

bayes_mod_predictions <- function(models, original_data) {

    prediction_list <- list()

    for (mod_name in names(models)) {
        # Extract mod
        mod_i <- models[[mod_name]]

        # Make mode predictions
        model_predictions_i <- as.data.frame(fitted(mod_i, summary = TRUE)) %>%
            dplyr::mutate(model = mod_name, id = sub("_(lm_\\d+)$", "", mod_name),
                model_type = sub("^.*_(lm_\\d+)$", "\\1", mod_name)) %>%
            dplyr::rename(pred_lower = Q2.5, pred_upper = Q97.5, predicted = Estimate,
                pred_error = Est.Error) %>%
            dplyr::select(model, everything())

        id_i <- model_predictions_i$id[1]

        original_data_i <- original_data %>%
            dplyr::filter(id == id_i)

        model_predictions_original_i <- cbind(model_predictions_i, original_data_i)

        prediction_list[[mod_name]] <- model_predictions_original_i
    }
    predictions_df <- bind_rows(prediction_list)
    return(predictions_df)
}

calcSMR: authored by Chabot D. used to estimate SMR with several different methods Claireaux and Chabot (2016) DOI: doi:10.1111/jfb.12833

calcSMR = function(Y, q = c(0.1, 0.15, 0.2, 0.25, 0.3), G = 1:4) {
    u = sort(Y)
    the.Mclust <- Mclust(Y, G = G)
    cl <- the.Mclust$classification
    # sometimes, the class containing SMR is not called 1 the following
    # presumes that when class 1 contains > 10% of cases, it contains SMR,
    # otherwise we take class 2
    cl2 <- as.data.frame(table(cl))
    cl2$cl <- as.numeric(levels(cl2$cl))
    valid <- cl2$Freq >= 0.1 * length(time)
    the.cl <- min(cl2$cl[valid])
    left.distr <- Y[the.Mclust$classification == the.cl]
    mlnd = the.Mclust$parameters$mean[the.cl]
    CVmlnd = sd(left.distr)/mlnd * 100
    quant = quantile(Y, q)
    low10 = mean(u[1:10])
    low10pc = mean(u[6:(5 + round(0.1 * (length(u) - 5)))])
    # remove 5 outliers, keep lowest 10% of the rest, average Herrmann & Enders
    # 2000
    return(list(mlnd = mlnd, quant = quant, low10 = low10, low10pc = low10pc, cl = cl,
        CVmlnd = CVmlnd))
}

calcO2crit: authored by Chabot D. used to estimate O2crit (Pcript). Claireaux and Chabot (2016) DOI: doi:10.1111/jfb.12833

Note: O2 is assumed to be in percentage of dissolved oxygen (DO) to work

calcO2crit <- function(Data, SMR, lowestMO2 = NA, gapLimit = 4, max.nb.MO2.for.reg = 20) {
    # AUTHOR: Denis Chabot, Institut Maurice-Lamontagne, DFO, Canada first
    # version written in June 2009 last updated in January 2015
    method = "LS_reg"  # will become 'through_origin' if intercept is > 0
    if (is.na(lowestMO2))
        lowestMO2 = quantile(Data$MO2[Data$DO >= 80], p = 0.05)
    # Step 1: identify points where MO2 is proportional to DO
    geqSMR = Data$MO2 >= lowestMO2
    pivotDO = min(Data$DO[geqSMR])
    lethal = Data$DO < pivotDO
    N_under_SMR = sum(lethal)  # points available for regression?
    final_N_under_SMR = lethal  # some points may be removed at Step 4
    lastMO2reg = Data$MO2[Data$DO == pivotDO]  # last MO2 when regulating
    if (N_under_SMR > 1)
        theMod = lm(MO2 ~ DO, data = Data[lethal, ])
    # Step 2, add one or more point at or above SMR 2A, when there are fewer
    # than 3 valid points to calculate a regression
    if (N_under_SMR < 3) {
        missing = 3 - sum(lethal)
        not.lethal = Data$DO[geqSMR]
        DOlimit = max(sort(not.lethal)[1:missing])  # highest DO acceptable
        # to reach a N of 3
        addedPoints = Data$DO <= DOlimit
        lethal = lethal | addedPoints
        theMod = lm(MO2 ~ DO, data = Data[lethal, ])
    }
    # 2B, add pivotDO to the fit when Step 1 yielded 3 or more values?
    if (N_under_SMR >= 3) {
        lethalB = Data$DO <= pivotDO  # has one more value than 'lethal'
        regA = theMod
        regB = lm(MO2 ~ DO, data = Data[lethalB, ])
        large_slope_drop = (coef(regA)[2]/coef(regB)[2]) > 1.1  # arbitrary
        large_DO_gap = (max(Data$DO[lethalB]) - max(Data$DO[lethal])) > gapLimit
        tooSmallMO2 = lastMO2reg < SMR
        if (!large_slope_drop & !large_DO_gap & !tooSmallMO2)
            {
                lethal = lethalB
                theMod = regB
            }  # otherwise we do not accept the additional point
    }
    # Step 3 if the user wants to limit the number of points in the regression
    if (!is.na(max.nb.MO2.for.reg) & sum(lethal) > max.nb.MO2.for.reg) {
        Ranks = rank(Data$DO)
        lethal = Ranks <= max.nb.MO2.for.reg
        theMod = lm(MO2 ~ DO, data = Data[lethal, ])
        final_N_under_SMR = max.nb.MO2.for.reg
    }
    # Step 4
    predMO2 = as.numeric(predict(theMod, data.frame(DO = Data$DO)))
    Data$delta = (Data$MO2 - predMO2)/predMO2 * 100  # residuals set to zero
    # when below pivotDO
    Data$delta[Data$DO < pivotDO | lethal] = 0
    tol = 0  # any positive residual is unacceptable
    HighValues = Data$delta > tol
    Ranks = rank(-1 * Data$delta)
    HighMO2 = HighValues & Ranks == min(Ranks)  # keep largest residual
    if (sum(HighValues) > 0)
        {
            nblethal = sum(lethal)
            Data$W = NA
            Data$W[lethal] = 1/nblethal
            Data$W[HighMO2] = 1
            theMod = lm(MO2 ~ DO, weight = W, data = Data[lethal | HighMO2, ])
            # This new regression is always an improvement, but there can still
            # be points above the line, so we repeat
            predMO2_2 = as.numeric(predict(theMod, data.frame(DO = Data$DO)))
            Data$delta2 = (Data$MO2 - predMO2_2)/predMO2_2 * 100
            Data$delta2[Data$DO < pivotDO] = 0
            tol = Data$delta2[HighMO2]
            HighValues2 = Data$delta2 > tol
            if (sum(HighValues2) > 0)
                {
                  Ranks2 = rank(-1 * Data$delta2)
                  HighMO2_2 = HighValues2 & Ranks2 == 1  # keep the largest residual
                  nblethal = sum(lethal)
                  Data$W = NA
                  Data$W[lethal] = 1/nblethal
                  Data$W[HighMO2_2] = 1
                  theMod2 = lm(MO2 ~ DO, weight = W, data = Data[lethal | HighMO2_2,
                    ])
                  # is new slope steeper than the old one?
                  if (theMod2$coef[2] > theMod$coef[2]) {
                    theMod = theMod2
                    HighMO2 = HighMO2_2
                  }
                }  # end second search for high value
        }  # end first search for high value
    Coef = coefficients(theMod)
    # Step 5, check for positive intercept
    AboveOrigin = Coef[1] > 0
    # if it is, we use a regression that goes through the origin
    if (AboveOrigin) {
        theMod = lm(MO2 ~ DO - 1, data = Data[lethal, ])
        Coef = c(0, coefficients(theMod))  # need to add the intercept (0)
        # manually to have a pair of coefficients
        method = "through_origin"
        HighMO2 = rep(FALSE, nrow(Data))  # did not use the additional value
        # from Step 4
    }
    po2crit = as.numeric(round((SMR - Coef[1])/Coef[2], 1))
    sum_mod = summary(theMod)
    anov_mod = anova(theMod)
    O2CRIT = list(o2crit = po2crit, SMR = SMR, Nb_MO2_conforming = N_under_SMR, Nb_MO2_conf_used = final_N_under_SMR,
        High_MO2_required = sum(HighMO2) == 1, origData = Data, Method = method,
        mod = theMod, r2 = sum_mod$r.squared, P = anov_mod$"Pr(>F)", lethalPoints = which(lethal),
        AddedPoints = which(HighMO2))
}  # end function

plotO2crit: used to plot the modes used for the calcO2crit function. Claireaux and Chabot (2016) DOI: doi:10.1111/jfb.12833

plotO2crit <- function(o2critobj, plotID = "", Xlab = "Dissolved oxygen (% sat.)",
    Ylab = "dotitalumol", smr.cex = 0.9, o2crit.cex = 0.9, plotID.cex = 1.2, Transparency = T,
    ...) {
    # AUTHOR: Denis Chabot, Institut Maurice-Lamontagne, DFO, Canada first
    # version written in June 2009 last updated 2015-02-09 for R plotting
    # devices that do not support transparency (e.g., postscript), set
    # Transparency to FALSE
    smr = o2critobj$SMR
    if (Ylab %in% c("dotitalumol", "italumol", "dotumol", "umol", "dotitalmg", "italmg",
        "dotmg", "mg")) {
        switch(Ylab, dotitalumol = {
            mo2.lab = expression(paste(italic(dot(M))[O[2]], " (", mu, "mol ", O[2],
                " ", min^-1, " ", kg^-1, ")"))
        }, italumol = {
            mo2.lab = expression(paste(italic(M)[O[2]], " (", mu, "mol ", O[2], " ",
                min^-1, " ", kg^-1, ")"))
        }, dotumol = {
            mo2.lab = expression(paste(dot(M)[O[2]], " (", mu, "mol ", O[2], " ",
                min^-1, " ", kg^-1, ")"))
        }, umol = {
            mo2.lab = expression(paste(M[O[2]], " (", mu, "mol ", O[2], " ", min^-1,
                " ", kg^-1, ")"))
        }, dotitalmg = {
            mo2.lab = expression(paste(italic(dot(M))[O[2]], " (mg ", O[2], " ",
                h^-1, " ", kg^-1, ")"))
        }, italmg = {
            mo2.lab = expression(paste(italic(M)[O[2]], " (mg ", O[2], " ", h^-1,
                " ", kg^-1, ")"))
        }, dotmg = {
            mo2.lab = expression(paste(dot(M)[O[2]], " (mg ", O[2], " ", h^-1, " ",
                kg^-1, ")"))
        }, mg = {
            mo2.lab = expression(paste(M[O[2]], " (mg ", O[2], " ", h^-1, " ", kg^-1,
                ")"))
        })
    } else mo2.lab = Ylab
    if (Transparency) {
        Col = c(rgb(0, 0, 0, 0.7), "red", "orange")
    } else {
        Col = c(grey(0.3), "red", "orange")
    }
    Data = o2critobj$origData
    lowestMO2 = quantile(Data$MO2[Data$DO >= 80], p = 0.05)  # I added this
    Data$Color = Col[1]
    Data$Color[o2critobj$lethalPoints] = Col[2]
    Data$Color[o2critobj$AddedPoints] = Col[3]
    # ordinary LS regression without added points: blue line, red symbols
    # ordinary LS regression with added points: blue line, red & orange symbols
    # regression through origin: green dotted line, red symbols
    line.color = ifelse(o2critobj$Method == "LS_reg", "blue", "darkgreen")
    line.type = ifelse(o2critobj$Method == "LS_reg", 1, 3)
    limX = c(0, max(Data$DO))
    limY = c(0, max(Data$MO2))
    plot(MO2 ~ DO, data = Data, xlim = limX, ylim = limY, col = Data$Color, xlab = Xlab,
        ylab = mo2.lab, ...)
    coord <- par("usr")
    if (plotID != "") {
        text(0, coord[4], plotID, cex = plotID.cex, adj = c(0, 1.2))
    }
    abline(h = lowestMO2, col = "pink")  # I added this
    abline(h = smr, col = "orange")
    text(coord[1], smr, "SMR", adj = c(-0.1, 1.3), cex = smr.cex)
    text(coord[1], smr, round(smr, 1), adj = c(-0.1, -0.3), cex = smr.cex)
    if (!is.na(o2critobj$o2crit)) {
        abline(o2critobj$mod, col = line.color, lty = line.type)
        segments(o2critobj$o2crit, smr, o2critobj$o2crit, coord[3], col = line.color,
            lwd = 1)
        text(x = o2critobj$o2crit, y = 0, o2critobj$o2crit, col = line.color, cex = o2crit.cex,
            adj = c(-0.1, 0.5))
    }
}  # end of function

Working directories

Input

meta_files_wd: Directory for the metadata

wd <- getwd()
meta_files_wd <- paste0(wd, "./meta-data")  # creates a variable with the name of the wd we want to use

labchart_wd: Directory for Labchart estimated slopes

labchart_wd <- paste0(wd, "./lab-chart-slopes")

mod_data_wd: Directory for model output data estimated slopes

mod_data_wd <- paste0(wd, "./mod-data")

Output

output_fig_wd: this is where we will put the figures

output_fig_wd <- paste0(wd, "./output-fig")
ifelse(!dir.exists("output-fig"), dir.create("output-fig"), "Folder already exists")
## [1] "Folder already exists"

output_mods_wd: this is where we will put the figures

output_mods_wd <- paste0(wd, "./output-mod")
ifelse(!dir.exists("output-mod"), dir.create("output-mod"), "Folder already exists")
## [1] "Folder already exists"

Input files

Slopes (MO2)

slope_df: We have imported the slopes extracted in LabChart during each phase of the experiment

 setwd(labchart_wd)
# 
# # Get the names of all sheets in the Excel file
sheet_names <- excel_sheets("labchart-all-dates_v2.xlsx")
all_trials_select <- c("start_date", "order", "phase", "cycle", "date", "time")
slope_list <- list()

for (sheet in sheet_names) {

  df <- read_excel("labchart-all-dates_v2.xlsx", sheet = sheet) %>% 
  dplyr::rename_with(tolower)
  
a_name <- paste0("a_", tolower(sheet))
a_df <- df %>%
  dplyr::select(starts_with('a'), all_trials_select) %>% 
  dplyr::rename(temp = a_temp) %>% 
  dplyr::mutate(across(starts_with('a'), as.numeric)) %>% 
  pivot_longer(
    cols = starts_with('a'), # Select all columns to pivot
    names_to = c("chamber_id", ".value"), # Separate column names into 'id' and other variables
    names_sep = "_"
  ) %>%
  dplyr::mutate(respirometer_group = "a") # Add a new column with a fixed value

slope_list[[a_name]]<- a_df

b_name <- paste0("b_", tolower(sheet))
b_df <- df %>% 
  dplyr::select(starts_with('b'), all_trials_select) %>% 
  dplyr::rename(temp = b_temp) %>% 
  dplyr::mutate(across(starts_with('b'), as.numeric)) %>% 
  pivot_longer(
    cols = starts_with('b'), # Select all columns to pivot
    names_to = c("chamber_id", ".value"), # Separate column names into 'id' and other variables
    names_sep = "_"
  ) %>% 
    dplyr::mutate(respirometer_group = "b")

slope_list[[b_name]] <- b_df

c_name <- paste0("c_", tolower(sheet))
c_df <- df %>% 
  dplyr::select(starts_with('c'), all_trials_select) %>% 
  dplyr::rename(temp = c_temp,
                i_cycle = cycle) %>% 
  dplyr::mutate(across(starts_with('c'), as.numeric)) %>%
  pivot_longer(
    cols = starts_with('c'), # Select all columns to pivot
    names_to = c("chamber_id", ".value"), # Separate column names into 'id' and other variables
    names_sep = "_"
  ) %>% 
    dplyr::mutate(respirometer_group = "c") %>% 
  dplyr::rename(cycle = i_cycle)

slope_list[[c_name]] <- c_df

d_name <- paste0("d_", tolower(sheet))
d_df <- df %>% 
  dplyr::select(starts_with('d'), all_trials_select) %>% 
  dplyr::rename(temp = d_temp,
                i_date = date) %>% 
  dplyr::mutate(across(starts_with('d'), as.numeric)) %>%
  pivot_longer(
    cols = starts_with('d'), # Select all columns to pivot
    names_to = c("chamber_id", ".value"), # Separate column names into 'id' and other variables
    names_sep = "_"
  ) %>% 
    dplyr::mutate(respirometer_group = "d") %>% 
  dplyr::rename(date = i_date)

slope_list[[d_name]] <- d_df
}


slope_df <- bind_rows(slope_list) %>% 
  dplyr::mutate(resp_cat_date = paste0(respirometer_group, "_", start_date),
                chamber_n = str_extract(chamber_id, "\\d+"),
                id_prox = paste0(resp_cat_date, "_", chamber_n),
                time_hms = as_hms(time*3600),
                date_chr = format(date, "%d/%m/%Y")
                )

Metadata

metadata: This is the meta data for each chamber

Note: We are also adding volume based on chamber type.

setwd(meta_files_wd)

metadata <- read_excel("Morpho.xlsx", na = "NA") %>%
    dplyr::mutate(id_split = id) %>%
    tidyr::separate(id_split, into = c("respirometer_group", "salinity_group", "start_date",
        "chamber"), sep = "_") %>%
    dplyr::mutate(volume = dplyr::case_when(chamber_type == "L" ~ 0.3, chamber_type ==
        "M_M" ~ 0.105, chamber_type == "M_NM" ~ 0.11, chamber_type == "S" ~ 0.058,
        chamber_type == "SM" ~ 0.075, chamber_type == "D3" ~ 0.055, TRUE ~ NA), id_prox = paste0(respirometer_group,
        "_", start_date, "_", chamber))

Combinding metadata

Adding the meta data to LabChart slopes

slope_df_2 <- slope_df %>%
    dplyr::select(-start_date, -respirometer_group) %>%
    left_join(metadata, by = "id_prox") %>%
    dplyr::mutate(light_dark = if_else(time_hms >= as.hms("07:00:00") & time_hms <
        as.hms("19:00:00"), "light", "dark")) %>%
    dplyr::arrange(id)

Data

Numbers

We have 64 fish with MO2 data

n <- slope_df_2 %>%
    dplyr::filter(chamber_condition == "fish") %>%
    dplyr::distinct(id) %>%
    nrow(.)

paste0("n = ", n)
## [1] "n = 64"


With 48 from the 0 ppt and 48 from 9 ppt groups

slope_df_2 %>%
    dplyr::group_by(salinity_group) %>%
    dplyr::reframe(`n total` = length(unique(id))) %>%
    gt() %>%
    cols_label(salinity_group = "Salinity group") %>%
    cols_align(align = "center", columns = everything())
Salinity group n total
0 48
9 48

Size

Here we caculate the mean length and size of fish used in the experiment.

mass_length <- slope_df_2 %>%
    dplyr::group_by(id) %>%
    dplyr::sample_n(1) %>%
    dplyr::ungroup() %>%
    dplyr::reframe(x_mass = round(mean(mass, na.rm = TRUE), 3), min_mass = round(min(mass,
        na.rm = TRUE), 3), max_mass = round(max(mass, na.rm = TRUE), 3), x_length = round(mean(length,
        na.rm = TRUE), 2), min_length = round(min(length, na.rm = TRUE), 2), max_length = round(max(length,
        na.rm = TRUE), 2))

mass_mean <- mass_length %>%
    pull(x_mass)

mass_min <- mass_length %>%
    pull(min_mass)

mass_max <- mass_length %>%
    pull(max_mass)

length_mean <- mass_length %>%
    pull(x_length)

length_min <- mass_length %>%
    pull(min_length)

length_max <- mass_length %>%
    pull(max_length)

paste0("The mean mass of fish was ", mass_mean, " g (range: ", mass_min, "–", mass_max,
    ")", ", and the mean length was ", length_mean, " mm (range: ", length_min, "–",
    length_max, ")")
## [1] "The mean mass of fish was 0.532 g (range: 0.21–1.6), and the mean length was 50.41 mm (range: 40–70)"

Filtering trials

We will remove 6 trials which had errors. These are as follows:

  • a_0_25nov_3 needs to be removed (fish died)
  • b_0_26nov_4 flat lined early
  • c_0_22nov_2 accidentally opened the chamber early
  • c_9_26nov_2 stopped trial early, took too long
  • c_9_26nov_4 stopped trial early, took too long
  • d_9_27nov_3 sensor was jumpy and end points were hard to confidently ID visually
remove_trial_error <- c("a_0_25nov_3", "b_0_26nov_4", "c_0_22nov_2", "c_9_26nov_2",
    "c_9_26nov_4", "d_9_27nov_3")

slope_df_filter <- slope_df_2 %>%
    dplyr::filter(!(id %in% remove_trial_error))

We now have 58 fish with MO2 data

n <- slope_df_filter %>%
    dplyr::filter(chamber_condition == "fish") %>%
    dplyr::distinct(id) %>%
    nrow(.)

paste0("n = ", n)
## [1] "n = 58"

With 45 in the 0 ppt group and 45 in the 9 ppt group

slope_df_filter %>%
    dplyr::group_by(salinity_group) %>%
    dplyr::reframe(`n total` = length(unique(id))) %>%
    gt() %>%
    cols_label(salinity_group = "Salinity group") %>%
    cols_align(align = "center", columns = everything())
Salinity group n total
0 45
9 45

Filtering MO2 estimates

Here we apply the following filters to the MO2 data:

  • Remove the first 5 SMR cycles (burn in)
  • Remove all positive raw slopes
  • Remove all MO2 calculated using less then 60 data points (5 min)
  • Remove all MO2 calculated if o2 increases in a closed phase (i.e. trial has ended)
cycle_burn <- 0:4

slope_df_filter_1 <- slope_df_filter %>%
    dplyr::filter(!(cycle %in% cycle_burn) & mo2corr < 0 & n > 60 & chamber_condition ==
        "fish")

# Now we remove the points after the chamber is opened This is a function to do
# so
filter_o2_increase <- function(group) {
    group <- group %>%
        mutate(o2_diff = o2 - lag(o2))  # Calculate the difference in 'o2'

    # Find the first index where 'o2_diff' exceeds 1
    cutoff_index <- which(group$o2_diff > 1)[1]

    # Filter the data up to the cutoff index, or return the full group if no
    # cutoff
    if (!is.na(cutoff_index)) {
        group <- group[1:(cutoff_index - 1), ]
    }

    return(group)
}

# Apply the function to each group of 'chamber_id'
slope_tidy_closed <- slope_df_filter_1 %>%
    dplyr::filter(phase != "smr") %>%
    group_by(id) %>%
    group_split() %>%
    lapply(filter_o2_increase) %>%
    bind_rows() %>%
    select(-o2_diff)

slope_tidy_smr <- slope_df_filter_1 %>%
    dplyr::filter(phase == "smr")

slope_df_filter_2 <- rbind(slope_tidy_smr, slope_tidy_closed) %>%
    dplyr::arrange(id, order)

Calculating SMR

We have estimated SMR with two different appraches.

First using the mean of the lowest 3 values (smr_3l_means)

smr_3l_means <- slope_df_filter_2 %>%
  dplyr::group_by(id) %>% 
  dplyr::filter(phase == "smr") %>%
  dplyr::arrange(desc(mo2corr)) %>%
  dplyr::slice_head(n = 3)  %>% # Select the three lowest MO2
  dplyr::ungroup() %>% 
  dplyr::group_by(id) %>% 
  dplyr::reframe(smr_l3 = mean(mo2corr))

# Combine the processed "smr" phase with all other phases
slope_df_filter_3 <- slope_df_filter_2 %>%
  dplyr::left_join(., smr_3l_means, by = "id")


Second using the calcSMR function by Chabot, Steffensen and Farrell (2016) DOI: 10.1111/jfb.12845. Specifically, We use mean of the lowest normal distribution (MLND) where CVmlnd < 5.4 and the mean of the lower 20% quantile (q0.2) were CVmlnd > 5.4. If CVmlnd is not calculated we have used q0.2.

labchart_chabot_smr <- slope_df_filter_3 %>%
    dplyr::filter(phase == "smr")

# Extract distinct IDs
ids <- labchart_chabot_smr %>%
    dplyr::distinct(id) %>%
    dplyr::pull()

# Initialise an empty list to store SMR data
smr_list <- list()

# Process each ID
for (id_i in ids) {
    tryCatch({
        # Filter the data for the current ID
        df_i <- labchart_chabot_smr %>%
            dplyr::filter(id == id_i) %>%
            dplyr::mutate(abs_mo2corr = abs(mo2corr))

        # Calculate SMR results
        calcSMR_results <- calcSMR(df_i$abs_mo2corr)
        CVmlnd_i <- calcSMR_results$CVmlnd
        quant_i <- calcSMR_results$quant %>%
            as_tibble()
        quant_20per_i <- quant_i$value[3]
        mlnd_i <- calcSMR_results$mlnd
        smr_value <- if_else(CVmlnd_i < 5.4, mlnd_i, quant_20per_i)
        smr_type <- if_else(CVmlnd_i < 5.4, "mlnd", "quant_20per")
        smr_value <- if_else(is.na(smr_value), quant_20per_i, smr_value)
        smr_type <- if_else(is.na(smr_type), "quant_20per", smr_type)

        # Create a data frame for the current ID
        smr_df <- tibble(id = id_i, smr = smr_value, smr_est = smr_type)

    }, error = function(e) {
        # Handle errors by assigning NA values
        smr_df <- tibble(id = id_i, smr = NA, smr_est = NA)
    })

    # Append to the list
    smr_list[[id_i]] <- smr_df
}

# Combine all individual SMR data frames into one
smr_df <- bind_rows(smr_list) %>%
    dplyr::rename(smr_chabot = smr, smr_chabot_method = smr_est)

slope_df_filter_4 <- slope_df_filter_3 %>%
    dplyr::left_join(., smr_df, by = "id")

Transforming MO2

Here we are transforming the MO2 units. The resulting vaules are as follows:

  • MO2 = absolute value of the background and leak corrected mo2 slope from Labchart (mo2corr) times the net volume of the chamber (volume - fish mass), times 60, times 60, to achieve mg O2 / h.
  • MO2_g = MO2 divided by fish mass to achieve mg O2 / g/ h (i.e. mass standardised)
  • SMR = absolute value of the mean of the three lowest MO2 during the SMR phase (smr_l3) times the net volume of the chamber (volume - fish mass), times 60, times 60, to achieve mg MO2 / h
  • SMR_g = SMR divided by fish mass
  • SMR_CHABOT = absolute value of the SMR estimates using methods descibed by Chabot, Steffensen and Farrell (2016) (smr_chabot)
  • SMR_g = SMR_CHABOT divided by fish mass
  • DO = dissolved oxygen percentage calculated from o2 values (mg/L) using the recorded temperature, salinity, and a constant atmospheric pressure (1013.25)
# Combine back into one data frame
slope_tidy <- slope_df_filter_4 %>% 
    dplyr::mutate(DO = conv_o2(
                   o2 = o2,
                   from = "mg_per_l",
                   to = "percent_a.s.",
                   temp = temp, #C
                   sal = measured_salinity,
                   atm_pres = 1013.25),
                  o2_kpa = conv_o2(
                   o2 = o2,
                   from = "mg_per_l",
                   to = "kPa",
                   temp = temp, #C
                   sal = measured_salinity,
                   atm_pres = 1013.25),
                  net_volume = volume - mass/1000,
                  MO2 = abs(mo2corr)*net_volume*60*60,
                  MO2_g = MO2/mass,
                  SMR = abs(smr_l3)*net_volume*60*60,
                  SMR_g = SMR/mass,
                  SMR_CHABOT = abs(smr_chabot)*net_volume*60*60,
                  SMR_CHABOT_g = SMR_CHABOT/mass
                  )

Visualising data

O2 vs MO2

Here we plot all oxygen consumption (MO2; mg O2/g/h) by dissolved oxygen percentage (DO) for all fish, including all SMR estimates.

slope_tidy %>% 
  ggplot(aes(y = MO2_g, x = DO, colour = id)) + # Default aesthetics
  geom_point(show.legend = FALSE) +
  geom_smooth(aes(group = id), method = "lm", se = FALSE, colour = scales::alpha("black", 0.5)) + # Transparent black lines
  geom_smooth(method = "lm", se = TRUE, colour = "red") + # Overall smooth line
  geom_smooth(se = TRUE, colour = "red", linetype = "dashed") +
  theme_clean() +
  labs(
    subtitle = "All values",
    x = "Dissolved oxygen percentage (DO)",
    y = "MO2 (mg O2 g/h)"
  )


Same plot but without SMR values.

slope_tidy %>% 
  dplyr::filter(phase != "smr") %>% 
  ggplot(aes(y = MO2_g, x = DO, colour = id)) + # Default aesthetics
  geom_point(show.legend = FALSE) +
  geom_smooth(aes(group = id), method = "lm", se = FALSE, colour = scales::alpha("black", 0.5)) + # Transparent black lines
  geom_smooth(method = "lm", se = TRUE, colour = "red") + # Overall smooth line
  geom_smooth(se = TRUE, colour = "red", linetype = "dashed") +
  theme_clean() +
  labs(
    subtitle = "Only closed periods",
    x = "Dissolved oxygen percentage (DO)",
    y = "MO2 (O2 mg/g/h)"
  )

Salinity


Looking at the difference responses in the two salinity groups. It’s appears more variable in freshwater.

slope_tidy %>% 
  ggplot(aes(y = MO2_g, x = DO, colour = id)) + # Default aesthetics
  geom_point(show.legend = FALSE) +
  geom_smooth(aes(group = id), method = "lm", se = FALSE, colour = scales::alpha("black", 0.5)) + # Transparent black lines
  geom_smooth(method = "lm", se = TRUE, colour = "red") + # Overall smooth line
  geom_smooth(se = TRUE, colour = "red", linetype = "dashed") +
  theme_clean() +
  facet_wrap(~salinity_group) +
  labs(
    subtitle = "mo2 vs o2 by salinity treatment",
    x = "Dissolved oxygen percentage (DO)",
    y = "MO2 (O2 mg/g/h)"
  )

Chambers


Looking at the difference chamber types

slope_tidy %>% 
  ggplot(aes(y = MO2_g, x = DO, colour = id)) + # Default aesthetics
  geom_point(show.legend = FALSE) +
  geom_smooth(aes(group = id), method = "lm", se = FALSE, colour = scales::alpha("black", 0.5)) + # Transparent black lines
  geom_smooth(method = "lm", se = TRUE, colour = "red") + # Overall smooth line
  geom_smooth(se = TRUE, colour = "red", linetype = "dashed") +
  theme_clean() +
  facet_wrap(~chamber_type, scale = "free") +
  labs(
    subtitle = "mo2 vs o2 by chamber type",
    x = "Dissolved oxygen percentage (DO)",
    y = "MO2 (mg O2 g/h)"
  )

Urbina et al. (2012) Comparison

Comparison to data from Urbina et al. (2012)

n <- slope_tidy %>% 
  dplyr::distinct(id) %>% 
  nrow(.)


min_o2_kpa <- min(slope_tidy$o2_kpa, na.rm = TRUE)
max_o2_kpa <- max(slope_tidy$o2_kpa, na.rm = TRUE)

slope_tidy <- slope_tidy %>%
  mutate(o2_group = cut(o2_kpa, 
                        breaks = seq(min_o2_kpa, max_o2_kpa, length.out = 13), # 11 intervals, so 12 breakpoints
                        labels = paste0("Group ", 1:12), 
                        include.lowest = TRUE))

time_bin_df <- slope_tidy %>% 
  dplyr::group_by(o2_group) %>% 
  dplyr::reframe(mean_MO2_g = mean(MO2_g)*31.25,
                 mean_o2_kpa = mean(o2_kpa),
                 n = length(MO2_g)*31.25,
                 MO2_g_sd = sd(MO2_g)*31.25,
                 o2_kpa_sd = sd(o2_kpa))

time_bin_df %>% 
  ggplot(aes(y = mean_MO2_g, x = mean_o2_kpa)) +
  # Add raw data points
  geom_point(data = slope_tidy, aes(y = MO2_g*31.25, x = o2_kpa), 
             size = 2, color = "grey", alpha = 0.5) +  # Raw data points
  # Add summary points
  geom_point(size = 3, colour = "black", show.legend = FALSE) +
  # Add vertical error bars
  geom_errorbar(aes(ymin = mean_MO2_g - MO2_g_sd, ymax = mean_MO2_g + MO2_g_sd), 
                width = 0.15, colour = "black") +
  # Add horizontal error bars
  geom_errorbarh(aes(xmin = mean_o2_kpa - o2_kpa_sd, xmax = mean_o2_kpa + o2_kpa_sd), 
                 height = 0.4, colour = "black") +
  annotate("text", x = 0, 
             y = 16, 
             label = paste0("n = ", n), 
             hjust = 0, vjust = 1, size = 4) +
  theme_clean() +
  labs(
    subtitle = "",
    x = "PO2 (kPa)",
    y = "MO2 (umol O2 g/h)"
  ) +
  scale_y_continuous(limits = c(0, 16), breaks = seq(0, 16, by = 2)) +
  scale_x_continuous(limits = c(0, 22), breaks = seq(0, 22, by = 2))  # Custom y-axis scale

SMR

Making an SMR only data frame

slope_tidy_smr <- slope_tidy %>%
    dplyr::filter(phase == "smr")

SMR by saility

Plot of SMR by salinity treatment. The small points are the observed values, the shaded area behind the points is the a kernel density of the observed data, the box plot on top show the median and interquartile range (IQR).

mean_mo2_salinity <- slope_tidy_smr %>% 
  dplyr::group_by(salinity_group) %>% 
  dplyr::reframe(mean_mo2 = mean(MO2, na.rm = TRUE))

fig_i <- ggplot() +
    geom_violin(data = slope_tidy_smr, aes(x = salinity_group, y = MO2, fill = salinity_group), color = NA, alpha = 0.3) +
  geom_jitter(data = slope_tidy_smr, aes(x = salinity_group, y = MO2, fill = salinity_group),
                       shape = 21, size = 2, color = "black", alpha = 0.2) +
  geom_boxplot(data = slope_tidy_smr, aes(x = salinity_group, y = MO2, fill = salinity_group),
                        size = 1, alpha = 0.5, outlier.shape = NA, width = 0.3) +
  geom_point(data = mean_mo2_salinity, 
                aes(x = salinity_group, y = mean_mo2, fill = salinity_group), 
                size = 3, alpha = 0.8, colour = "black", stroke = 2) +
  scale_fill_manual(values = c("#4B5320", "#000080")) +  # Custom fill colours
  scale_colour_manual(values = c("#4B5320", "#000080")) +
  theme_clean() +
  theme(legend.position = "none") +
  labs(
    subtitle = "",
    x = "Salinity group (ppt)",
    y = "MO2 (mg O2 g/h)"
  )

fig_i

SMR for each fish

Plotting MO2 estimates for each fish. The dashed red line is Chabot SMR methods, and the solid line is the mean of the lowest 3 measures (excluding the first 5 cycles)

# Create output directory if needed
output_fig_slopes_wd <- file.path(output_fig_wd, "slopes")
if (!dir.exists(output_fig_slopes_wd)) {
    dir.create(output_fig_slopes_wd)
}

ids <- slope_tidy %>%
    dplyr::distinct(id) %>%
    pull(id) %>%
    as.list()

MO2_plot_list <- list()

# 1) Open the PDF device once
pdf(file = file.path(output_fig_slopes_wd, "combined_slopes.pdf"), width = 8, height = 6)

# 2) Loop over IDs and create each plot
for (id_i in ids) {

    smr_chabot <- slope_tidy %>%
        dplyr::filter(id == id_i) %>%
        dplyr::slice(1) %>%
        dplyr::pull(SMR_CHABOT)

    smr_l3 <- slope_tidy %>%
        dplyr::filter(id == id_i) %>%
        dplyr::slice(1) %>%
        dplyr::pull(SMR)

    plot <- slope_tidy %>%
        dplyr::filter(id == id_i) %>%
        ggplot(aes(x = o2, y = MO2)) + geom_hline(yintercept = smr_chabot, linetype = "dashed",
        color = "darkred") + geom_hline(yintercept = smr_l3, color = "darkred") +
        geom_point(aes(colour = phase)) + theme_clean() + labs(subtitle = paste0(id_i,
        " slopes"), x = "Mean o2 (mg_per_l)", y = "abs(mo2) (mg_per_l)")

    # Instead of saving each plot separately, just print it
    print(plot)

    MO2_plot_list[[id_i]] <- plot
}

# 3) Close the PDF device *after* the loop
dev.off()
## png 
##   2
for (p in MO2_plot_list) {
    print(p)
}

Analysis

SMR

Scaling predictors

Here we scale our predictors for the model

center_list <- c("temp", "order", "mass")

slope_tidy_smr <- slope_tidy_smr %>%
    dplyr::mutate(across(all_of(center_list), ~scale(.x, center = TRUE, scale = FALSE),
        .names = "{.col}_z"), light_dark_c = if_else(light_dark == "light", 1, 0))

Model structure

Here we will use a Bayesian Generalised Linear Mixed Model (GLMM) with a Gamma distribution and a log link, where the shape parameter (K) is also modelled as a function of predictors. This models MO2 by salinity during the SMR phase to see if the fish held at different salinities have different SMRs. We have also added a few scaled predictors, that may help describe variation in the data, such as mass (g; 0.21–1.6) temperature (°C; 13.841–14.277), measurement order (1–28), and light/dark cycle (light or dark; light between 07:00:00 and 19:00:00), we also include a random effect for fish id to account for multiple MO2 measures on each fish. We allowed the the shape parameter (K) to vary as a function of some of the predictors (e.g. salinity_group, order_z) to improve fit.

smr_gamma_bf <- bf(MO2 ~ temp_z + order_z + light_dark_c + mass_z + salinity_group +
    (1 | id), shape ~ salinity_group + order_z, family = Gamma(link = "log"))

Prior selection

These are the default priors. We will use these.

suppressWarnings(get_prior(smr_gamma_bf, data = slope_tidy_smr, family = Gamma(link = "log")))
##                    prior     class            coef group resp  dpar nlpar lb ub
##                   (flat)         b                                             
##                   (flat)         b    light_dark_c                             
##                   (flat)         b          mass_z                             
##                   (flat)         b         order_z                             
##                   (flat)         b salinity_group9                             
##                   (flat)         b          temp_z                             
##  student_t(3, -2.7, 2.5) Intercept                                             
##     student_t(3, 0, 2.5)        sd                                         0   
##     student_t(3, 0, 2.5)        sd                    id                   0   
##     student_t(3, 0, 2.5)        sd       Intercept    id                   0   
##                   (flat)         b                            shape            
##                   (flat)         b         order_z            shape            
##                   (flat)         b salinity_group9            shape            
##     student_t(3, 0, 2.5) Intercept                            shape            
##        source
##       default
##  (vectorized)
##  (vectorized)
##  (vectorized)
##  (vectorized)
##  (vectorized)
##       default
##       default
##  (vectorized)
##  (vectorized)
##       default
##  (vectorized)
##  (vectorized)
##       default

Run model

Here we run the modle, I have hased this out because I have saved the model for quick reloading.

# setwd(output_mods_wd) smr_mod_gamma <- brm(smr_gamma, data = slope_tidy_smr,
# cores = 4, chains = 4, warmup = 1000, seed = 143019, thin = 2, iter = 8000,
# save_pars = save_pars(all=TRUE), sample_prior = TRUE, file = 'smr_mod_gamma')
# print('Model complete')

Here we reload the model

setwd(output_mods_wd)

smr_mod_gamma <- readRDS(file = "smr_mod_gamma.rds")

Checking model convergence

plot(smr_mod_gamma, ask = F)

Checking rhat are equal to one

tidy(rhat(smr_mod_gamma)) %>%
    dplyr::rename(rhat = x)
## # A tibble: 133 × 2
##    names                    rhat
##    <chr>                   <dbl>
##  1 b_Intercept              1.00
##  2 b_shape_Intercept        1.00
##  3 b_temp_z                 1.00
##  4 b_order_z                1.00
##  5 b_light_dark_c           1.00
##  6 b_mass_z                 1.00
##  7 b_salinity_group9        1.00
##  8 b_shape_salinity_group9  1.00
##  9 b_shape_order_z          1.00
## 10 sd_id__Intercept         1.00
## # ℹ 123 more rows

Using leave one out (loo) measure of fit, the model appears to preform well, all Pareto k estimates are good (k < 0.7)

loo(smr_mod_gamma)
## 
## Computed from 14000 by 895 log-likelihood matrix.
## 
##          Estimate   SE
## elpd_loo   2256.5 38.6
## p_loo        71.5  7.0
## looic     -4513.0 77.2
## ------
## MCSE of elpd_loo is 0.1.
## MCSE and ESS estimates assume MCMC draws (r_eff in [0.7, 1.0]).
## 
## All Pareto k estimates are good (k < 0.7).
## See help('pareto-k-diagnostic') for details.

Model predictions generally align with the observed data

plot <- pp_check(smr_mod_gamma, type = "dens_overlay")
plot

Results

We did not see a meaningful difference between the SMR measurements of fish from the two salinity treatments.

Table 1

Table 1: Fixed effect Estimates (β) and 95% Credible Intervals (95% CI) from a

model_est <- fixef(smr_mod_gamma, probs = c(0.025, 0.975)) %>%
    as.data.frame() %>%
    tibble::rownames_to_column(var = "Predictor") %>%
    dplyr::mutate(β = round(Estimate, 3), Q2.5 = round(Q2.5, 3), Q97.5 = round(Q97.5,
        3), `95% CI` = paste0("[", Q2.5, ", ", Q97.5, "]"))

model_est %>%
    dplyr::select(Predictor, "β", "95% CI") %>%
    gt()
Predictor β 95% CI
Intercept -2.615 [-2.739, -2.494]
shape_Intercept 2.568 [2.43, 2.7]
temp_z -0.344 [-0.768, 0.076]
order_z 0.001 [-0.002, 0.005]
light_dark_c 0.128 [0.08, 0.174]
mass_z 1.237 [0.908, 1.566]
salinity_group9 -0.096 [-0.272, 0.082]
shape_salinity_group9 0.179 [-0.02, 0.379]
shape_order_z -0.042 [-0.061, -0.024]

Looking at the marginal mean difference between salinity groups

em_results <- emmeans(smr_mod_gamma, ~salinity_group)
contrast_results <- contrast(em_results, method = "pairwise")
em_results_df <- em_results %>%
    tidy() %>%
    mutate(across(where(is.numeric), ~exp(.)))
contrast_results_df <- contrast_results %>%
    tidy() %>%
    mutate(across(where(is.numeric), ~exp(.)))


em_results_df %>%
    gt()
salinity_group estimate lower.HPD upper.HPD
0 0.07805491 0.06933489 0.08823359
9 0.07092322 0.06228137 0.08069857
emmeans_draws <- smr_mod_gamma %>%
    emmeans(~salinity_group) %>%
    gather_emmeans_draws() %>%
    dplyr::mutate(.value = exp(.value), salinity_group = as.character(salinity_group))

emmeans_contrast_draws <- smr_mod_gamma %>%
    emmeans(~salinity_group) %>%
    contrast(method = "pairwise") %>%
    gather_emmeans_draws() %>%
    dplyr::mutate(.value = exp(.value))

round_vars = c(".value", ".lower", ".upper")

smr_emmeans <- emmeans_draws %>%
    mean_qi(.width = c(0.889, 0.949)) %>%
    mutate(across(all_of(round_vars), ~round(.x, digits = 3)))

smr_contrast <- emmeans_contrast_draws %>%
    mean_qi(.width = c(0.889, 0.949)) %>%
    mutate(across(all_of(round_vars), ~round(.x, digits = 3)))

smr_emmeans
## # A tibble: 4 × 7
##   salinity_group .value .lower .upper .width .point .interval
##   <chr>           <dbl>  <dbl>  <dbl>  <dbl> <chr>  <chr>    
## 1 0               0.078  0.071  0.086  0.889 mean   qi       
## 2 9               0.071  0.064  0.079  0.889 mean   qi       
## 3 0               0.078  0.069  0.088  0.949 mean   qi       
## 4 9               0.071  0.062  0.081  0.949 mean   qi
smr_contrast
## # A tibble: 2 × 7
##   contrast                          .value .lower .upper .width .point .interval
##   <chr>                              <dbl>  <dbl>  <dbl>  <dbl> <chr>  <chr>    
## 1 salinity_group0 - salinity_group9   1.10  0.953   1.27  0.889 mean   qi       
## 2 salinity_group0 - salinity_group9   1.10  0.922   1.31  0.949 mean   qi

Fig 1

Discuss - what data to plot and what formate

Figure 1: The MO2 (mg o2 g/h) during SMR meassurments plotted by salinity treatment. The small transparent points are the observed values, the shaded area behind the points is the a kernel density of the observed data, the large coloured point (to the right) is the observed mean, the large grey point with error bars (to the left) is the model estimated marginal mean (eemean) 95% Credible Intervals (95% CI).

mean_mo2_salinity <- slope_tidy_smr %>% 
  dplyr::group_by(salinity_group) %>% 
  dplyr::reframe(mean_mo2 = mean(MO2, na.rm = TRUE))


fig_1 <- ggplot() +
  geom_violin(data = slope_tidy_smr,
              aes(x = salinity_group, y = MO2, fill = salinity_group),
              color = NA, alpha = 0.2) +
  geom_jitter(data = slope_tidy_smr,
              aes(x = salinity_group, y = MO2, fill = salinity_group),
              shape = 21, width = 0.3, size = 1, color = "black", alpha = 0.1) +
    geom_point(data = mean_mo2_salinity,
             aes(x = salinity_group, y = mean_mo2, fill = salinity_group),
             size = 4, alpha = 1, stroke = 2, color = "black", shape = 21,
              position = position_nudge(x = 0.05)) +
  stat_pointinterval(data = emmeans_draws, 
                     aes(x = salinity_group, y = .value),
                     color = "black", fill = "grey", point_interval = "mean_qi", .width = 0.95, shape = 21,  stroke = 2, point_size = 4, alpha = 1,
                     position = position_nudge(x = -0.05)) +
  scale_fill_manual(values = c("#4B5320", "#000080")) +  # Custom fill colours
  scale_colour_manual(values = c("#4B5320", "#000080")) +
  theme_clean() +
  theme(legend.position = "none") +
  labs(
    subtitle = "",
    x = "Salinity group (ppt)",
    y = "MO2 (mg O2 g/h)"
  )

fig_1

Incremental regression analyses

Here we are following the methods Urbina et al. (2012) with an incremental regression analyses, in order to determine the best fit for the data.

This analysis evaluated each polynomial order equation starting at zero and then increasing to the third order. This permitted a mathematical assessment of whether the data best fitted a single linear relationship (0th-order polynomial; suggesting the fish were oxyconforming and do not reach a Pcrit), or whether a PO2 crit value could be determined as an intersection point of two distinct functions (one at hypoxic oxygen concentrations, the other at normoxic; i.e. oxyregulation).

Building Bayesian regressions

Here we are using a Bayesian approach to model fitting with brm. These models take a long time to run, so I have saved them and re-loaded them to save time. I have also saved the summary data produced from the models, to save time, you can simply skip the hashed code and input the resulting summary data.

We will run our custom function, bayes_incremental_regression_by_id. This code takes a while to run. If you have already run this once, or have downloaded the saved models from GitHub skip this step (that’s why its hashed out), and run the next line, which loads the models.

# output_mods_bayes_wd <- paste0(output_mods_wd, './bayes-regs')
# ifelse(!dir.exists(output_mods_bayes_wd), dir.create(output_mods_bayes_wd),
# 'Folder already exists') ids <- slope_tidy %>% dplyr::distinct(id) %>%
# pull(id) plan(multisession) future_map( ids,
# bayes_incremental_regression_by_id, id_name = 'id', data = slope_tidy,
# response = 'MO2_g', predictor = 'DO', save_models = TRUE, mod_output_wd =
# output_mods_bayes_wd ) plan(sequential)

Load all models and store in a list, will use a lot of memory. You can also skip this step and load the reulsting data frames below. I am using the custom function load_rds, so we can compare them and generate predictions.

# bayes_reg_mods <- load_rds(model_dw = output_mods_bayes_wd)

Model fits

Get model fit parameters loo and r2 using the custom function, incremental_regression_bayes_fits.

# setwd(mod_data_wd) bayes_reg_mods_fit <-
# incremental_regression_bayes_fits(models = bayes_reg_mods)
# write.csv(bayes_reg_mods_fit, 'bayes_reg_mods_fit.csv', row.names = FALSE)

Reading in this model fit data frame, in the case you did not load in all the models.

setwd(mod_data_wd)
bayes_reg_mods_fit <- read.csv("bayes_reg_mods_fit.csv")

Selecting the best fitting model

elpd_loo, or the expected log pointwise predictive density for leave-one-out cross-validation, is a metric used in Bayesian model evaluation to assess the predictive accuracy of a model.

The elpd_loo is an approximation of how well the model is expected to predict new data, based on leave-one-out cross-validation. Higher elpd_loo values indicate better predictive performance.

best_fit_bayes_reg <- bayes_reg_mods_fit %>%
    dplyr::group_by(id) %>%
    dplyr::mutate(elpd_loo_rank = rank(-elpd_loo)) %>%
    dplyr::select(id, model_type, elpd_loo, r2, elpd_loo_rank, r2_q2.5, r2_q97.5,
        estimate_DO, conf.low_DO, conf.high_DO)

Model predictions

Pulling our model predictions using a custom function bayes_mod_predictions.

# setwd(mod_data_wd) bayes_reg_mods_predictions <- bayes_mod_predictions(models
# = bayes_reg_mods, original_data = slope_tidy)
# write.csv(bayes_reg_mods_predictions, 'bayes_reg_mods_predictions.csv',
# row.names = FALSE)

Reading in the predicted data

setwd(mod_data_wd)
bayes_reg_mods_predictions <- read.csv("bayes_reg_mods_predictions.csv")

We are going to combined this with our best fitting model df, so we know how they ranks for LOO.

bayes_reg_mods_predictions <- full_join(bayes_reg_mods_predictions, best_fit_bayes_reg,
    by = c("id", "model_type"))

Model selection summary

The best fitting models were most often a 2nd-order polynomial (n = 22, 38%) or a 3rd-order polynomial (n = 16, 28%). This could suggest the presence of a critical oxygen threshold (Pcrit) where the relationship between o2 and MO2 changes. To confirm their is a Pcrit, we need to validated the shape of the polynomials and in should use a more specific model to test the Pcrit value. In any case, This type of model is indicative of oxyregulator.

The next most common are 0th-order and 1st-order polynomials (both n = 10, 17%). In the case of the 0th-order model, it suggests that MO2 does not show a statistically significant dependence on the o2. In other words, the metabolic rate does not adjust based on oxygen availability, and there is no clear critical oxygen threshold (Pcrit) where the relationship changes. This is indicative of a oxyregulator. In the case of the 1st-order polynomials, it suggest the presences of linear relationship between o2 and MO2, which is indicative of oxyconformer. However, to be true evidence of a oxyconformer this relationship should be positive (i.e. as o2 falls MO2 also falls). Only 2 of the 10 individuals best modelled with a linear function had positive estimates with credible intervals that did not overlap with zero (Table 1).

best_mod <- best_fit_bayes_reg %>%
    dplyr::filter(elpd_loo_rank == 1)

total_fish <- nrow(best_mod)

table_bwm <- best_mod %>%
    dplyr::group_by(model_type) %>%
    dplyr::reframe(n = length(id), percent = round((n/total_fish) * 100, 2)) %>%
    dplyr::mutate(best_model_name = case_when(model_type == "lm_0" ~ "0th-order polynomial",
        model_type == "lm_1" ~ "1st-order polynomial", model_type == "lm_2" ~ "2nd-order polynomial",
        model_type == "lm_3" ~ "3rd-order polynomial", TRUE ~ "ERROR")) %>%
    dplyr::select(best_model_name, everything(), -model_type)


table_bwm %>%
    gt() %>%
    cols_align(align = "center", columns = everything())
best_model_name n percent
0th-order polynomial 10 17.24
1st-order polynomial 10 17.24
2nd-order polynomial 22 37.93
3rd-order polynomial 16 27.59

Summary of fish best model with a linear function.

table_lm_1 <- best_mod %>%
    dplyr::filter(model_type == "lm_1") %>%
    dplyr::mutate(r_sq_ci = paste0(round(r2, 3), " (", round(r2_q2.5, 3), "–",
        round(r2_q97.5, 3), ")"), est_ci = paste0(round(estimate_DO, 6), " (", round(conf.low_DO,
        6), "–", round(conf.high_DO, 6), ")"), conformer = if_else(conf.low_DO >
        0, "Conforming", "Not conforming")) %>%
    dplyr::select(id, r_sq_ci, est_ci, conformer) %>%
    dplyr::arrange(conformer) %>%
    dplyr::ungroup()


table_lm_1 %>%
    gt() %>%
    cols_align(align = "center", columns = everything()) %>%
    cols_label(id = "Fish ID", r_sq_ci = "r2 (CI)", est_ci = "Estimate (CI)", conformer = "Evidence of oxyconforming")
Fish ID r2 (CI) Estimate (CI) Evidence of oxyconforming
a_9_22nov_4 0.312 (0.094–0.491) 0.000849 (0.000422–0.001286) Conforming
d_9_25nov_3 0.197 (0.012–0.394) 0.001093 (0.000262–0.001907) Conforming
a_0_24nov_1 0.029 (0–0.134) 0.00017 (-0.000508–0.000862) Not conforming
a_0_24nov_3 0.058 (0–0.202) 0.000468 (-0.000291–0.001217) Not conforming
a_0_25nov_1 0.137 (0.002–0.331) -0.000358 (-0.000702–-1.5e-05) Not conforming
a_9_22nov_1 0.084 (0–0.266) 0.000324 (-0.000168–0.000814) Not conforming
b_0_25nov_2 0.108 (0.001–0.29) 0.000297 (-3e-05–0.000622) Not conforming
c_0_22nov_3 0.072 (0–0.256) 0.000531 (-0.000498–0.001572) Not conforming
c_9_27nov_2 0.068 (0–0.255) 0.000266 (-0.000304–0.000848) Not conforming
d_9_25nov_2 0.33 (0.102–0.507) -0.000891 (-0.001339–-0.000444) Not conforming

Ploting models

Now we are plotting each of the regressions. First making a directory to save the figures

incremental_reg_bayes_wd <- file.path(output_fig_wd, "incremental_regressions./bayes")
if (!dir.exists(incremental_reg_bayes_wd)) {
    dir.create(incremental_reg_bayes_wd)
}

Ploting all regression, and highlighting the model that has the best fit, based on AIC values

# Create a list to store the plots
plots <- list()
model_preds_list <- list()

for (id_i in ids) {
  
  # Filter data for the current ID
  df_i <- bayes_reg_mods_predictions %>%
    dplyr::filter(id == id_i) %>% 
    dplyr::mutate(line_size = if_else(elpd_loo_rank == 1, 2, 1),
           alpha_value = if_else(elpd_loo_rank == 1, 1, 0.4))
  
  x_min <- df_i %>%
    dplyr::reframe(min = min(DO), na.rm = TRUE) %>% 
    dplyr::pull(min)
  
  y_max <- df_i %>%
    dplyr::reframe(max = max(MO2_g), na.rm = TRUE) %>% 
    dplyr::pull(max)
  
  best_weighted_model_i <- best_fit_bayes_reg %>% 
    dplyr::filter(id == id_i & elpd_loo_rank == 1)
  
  poly_i_name <- best_weighted_model_i %>%
    dplyr::mutate(name = case_when(
      model_type == "lm_0" ~ "0th-order polynomial",
      model_type == "lm_1" ~ "1st-order polynomial",
      model_type == "lm_2" ~ "2nd-order polynomial",
      model_type == "lm_3" ~ "3rd-order polynomial",
      TRUE ~ "ERROR"
    )) %>% 
    dplyr::pull(name)
  
  r_i <- best_weighted_model_i %>% 
    dplyr::mutate(r_sq_ci = paste0(round(r2, 3), " (", 
                                    round(r2_q2.5, 3), "–", 
                                    round(r2_q97.5, 3), ")")) %>% 
    dplyr::pull(r_sq_ci)

  # Create the plot
  p <- ggplot() +
    geom_ribbon(data = df_i,
                aes(x = DO, y = predicted, ymin = pred_lower, ymax = pred_upper, fill = model_type), alpha = 0.1) +
    geom_line(data = df_i, 
              aes(x = DO, y = predicted, colour = model_type, size = line_size, alpha = alpha_value)) +
    geom_point(data = df_i %>% dplyr::filter(elpd_loo_rank == 1), aes(x = DO, y = MO2_g), alpha = 0.6, colour = "black", size = 2) +
    scale_colour_manual(values = c("red", "blue", "green", "purple"), 
                        labels = c("0th Order", "1st Order", "2nd Order", "3rd Order")) +
    scale_size_identity() +  # Use the size values directly
    scale_alpha_identity(guide = "none") +  # Remove the alpha legend 
    annotate("text", x = x_min, 
             y = y_max, 
             label = paste0("Best fit: ",poly_i_name, "\n", "r2 = ", r_i), 
             hjust = 0, vjust = 1, size = 4) +
    labs(
      title = paste("Model Fits vs Raw Data for ID", id_i),
      x = "Dissolved oxygen percentage (DO)",
      y = "MO2 (o2 mg/g/h)",
      colour = "Model") +
    theme_classic()
  
  # Store the plot
  plots[[id_i]] <- p
  
  print(p)
}

#To save all plots to individual files
for (id_i in ids) {
  ggsave(filename = paste0(incremental_reg_bayes_wd, "./plot_", id_i, ".png"), plot = plots[[id_i]], width = 8, height = 6)
}

#HERE

output_mods_bayes_global_wd <- paste0(output_mods_wd, "./bayes-regs-global")
ifelse(!dir.exists(output_mods_bayes_global_wd), dir.create(output_mods_bayes_global_wd),
    "Folder already exists")
## [1] "Folder already exists"

##Need only best fitting model Here we are grouping fish by best fitting model and getting an average trend

# best_fit <- bayes_reg_mods_predictions %>% dplyr::filter(elpd_loo_rank == 1)
# ids <- best_fit %>% dplyr::distinct(model_type) %>% pull(model_type)
# plan(multisession) future_map( ids, bayes_incremental_regression_by_id,
# id_name = 'model_type', data = best_fit, response = 'MO2_g', predictor =
# 'DO', save_models = TRUE, mod_output_wd = output_mods_bayes_global_wd )
# plan(sequential)
bayes_reg_mods <- load_rds(model_dw = output_mods_bayes_global_wd)
# ggplot() + geom_ribbon(data = df_i, aes(x = DO, y = predicted, ymin =
# pred_lower, ymax = pred_upper, fill = model_type), alpha = 0.1) +
# geom_line(data = df_i, aes(x = DO, y = predicted, colour = model_type, size =
# line_size, alpha = alpha_value)) + geom_point(data =
# bayes_reg_mods_predictions %>% dplyr::filter(elpd_loo_rank == 1), aes(x = DO,
# y = MO2_g), alpha = 0.1, colour = 'black', size = 1) + geom_line(data =
# bayes_reg_mods_predictions %>% dplyr::filter(elpd_loo_rank == 1), aes(x = DO,
# y = predicted, by = id), alpha = 0.2) + scale_colour_manual(values = c('red',
# 'blue', 'green', 'purple'), labels = c('0th Order', '1st Order', '2nd Order',
# '3rd Order')) + annotate('text', x = x_min, y = y_max, label = paste0('Best
# fit: ',poly_i_name, '\n', 'r2 = ', r_i), hjust = 0, vjust = 1, size = 4) +
# facet_wrap(~model_type) + labs( title = paste('Model Fits vs Raw Data for
# ID', id_i), x = 'Dissolved oxygen percentage (DO)', y = 'MO2 (mg o2/g/h)',
# colour = 'Model') + theme_classic()
# global_models <- list( lm_0 = lmer(MO2_g ~ 1 + (1|id), data = slope_tidy %>%
# dplyr::filter(poly == 0), weights = weight_smr), lm_1 = lmer(MO2_g ~ DO +
# (1|id), data = slope_tidy %>% dplyr::filter(poly == 1), weights =
# weight_smr), lm_2 = lmer(MO2_g ~ poly(DO, 2) + (1|id), data = slope_tidy %>%
# dplyr::filter(poly == 2), weights = weight_smr), lm_3 = lmer(MO2_g ~ poly(DO,
# 3) + (1|id), data = slope_tidy %>% dplyr::filter(poly == 3), weights =
# weight_smr) ) global_predictions <- data.frame(DO = seq(min(slope_tidy$DO),
# max(slope_tidy$DO), length.out = 100)) for (model_name in
# names(global_models)) { predictions <- predict( global_models[[model_name]],
# newdata = global_predictions, re.form = NA, # Excludes random effects
# (population-level predictions) se.fit = TRUE # Returns standard errors )
# global_predictions[[paste0(model_name, '_fit')]] <- predictions$fit
# global_predictions[[paste0(model_name, '_lwr')]] <- predictions$fit - 1.96 *
# predictions$se.fit global_predictions[[paste0(model_name, '_upr')]] <-
# predictions$fit + 1.96 * predictions$se.fit } global_predictions_long <-
# global_predictions %>% pivot_longer( cols =
# matches('lm_.*_fit|lm_.*_lwr|lm_.*_upr'), names_to = c('model', '.value'),
# names_pattern = '(lm_\\d+)_(.*)' ) %>% dplyr::mutate(best_model_name =
# case_when( model == 'lm_0' ~ '0th-order polynomial', model == 'lm_1' ~
# '1st-order polynomial', model == 'lm_2' ~ '2nd-order polynomial', model ==
# 'lm_3' ~ '3rd-order polynomial', TRUE ~ 'ERROR' ))

Figure

# best_weighted_model_pred <- best_weighted_model %>% dplyr::left_join(.,
# model_preds_df, by = c('id', 'model')) %>% dplyr::ungroup() %>%
# dplyr::mutate(best_model_name = case_when( poly == 0 ~ '0th-order
# polynomial', poly == 1 ~ '1st-order polynomial', poly == 2 ~ '2nd-order
# polynomial', poly == 3 ~ '3rd-order polynomial', TRUE ~ 'ERROR' ))
# annotation_data <- table_bwm %>% dplyr::select(best_model_name, n) fig_1 <-
# ggplot() + geom_line(data = best_weighted_model_pred, aes(x = DO, y =
# MO2_pred, color = id), size = 1, alpha = 1) + geom_point(data = slope_tidy,
# aes(x = DO, y = MO2_g), alpha = 0.1, colour = 'black', size = 2) +
# geom_ribbon(data = global_predictions_long, aes(x = DO, ymin = lwr, ymax =
# upr, group = model), fill = '#FC6C85', alpha = 0.2) + # Shaded confidence
# intervals geom_line(data = global_predictions_long, aes(x = DO, y = fit),
# size = 1.5, color = '#FF007F') + facet_wrap(~best_model_name) +
# scale_color_grey(start = 0.1, end = 0.9) + labs( title = paste('Model
# estimates and observed data grouped by best fitting model'), x = 'Dissolved
# oxygen percentage (DO)', y = 'MO2 (O2 mg/g/h)') + theme_classic() +
# theme(legend.position = 'none') + geom_text(data = annotation_data, aes(x =
# -Inf, y = Inf, label = paste0('italic(n) == ', n)), hjust = -0.1, vjust =
# 1.2, inherit.aes = FALSE, parse = TRUE) fig_1

Pcrit Chabot method

For those fish that were best modelled with a 2nd or 3rd-order polynomial (n = 46) we will check to see if a Pcrit is present. We are filtering the data for only those fish.

check_pcrit_ids <- bayes_reg_mods_predictions %>%
    dplyr::filter(model_type == "lm_1") %>%
    dplyr::distinct(id) %>%
    dplyr::pull(id)

check_pcrit_df <- slope_tidy %>%
    dplyr::filter(id %in% check_pcrit_ids)

We will calculate Pcrit using Chabot method and function calcO2crit. We are using our estimates for SMR (mean of lowest three).

This function uses the fifth percentile of the MO2 values observed at dissolved oxygen levels ≥ 80% air saturation as the criterion to assess low MO2 values. The algorithm then identifies all the MO2 measurements greater than this minimally acceptable MO2 value. Within this sub-set, it identifies the ̇ MO2 measurement made at the lowest DO and thereafter considers this DO as candidate for breakpoint (named pivotDO in the script). A regression is then calculated using observations at DO levels < pivotDO, and a first estimate of O2crit is calculated as the intersection of this regression line with the horizontal line representing SMR. The script then goes through validation steps to ensure that the slope of the regression is not so low that the line, projected to normoxic DO levels, passes below any MO2 values observed in normoxia. It also ensures that the intercept is not greater than zero. Corrective measures are taken if such problems are encountered.

lowestMO2 default is the quantile(Data\(MO2[Data\)DO >= 80], p=0.05). It is used to segment the data and locate the pivotDO.

ids <- check_pcrit_df %>%
    dplyr::distinct(id) %>%
    dplyr::pull()

pcrit_model_df_list <- list()
pcrit_models <- list()

for (id_i in ids) {

    df_i <- check_pcrit_df %>%
        dplyr::filter(id == id_i)

    o2crit <- calcO2crit(Data = df_i, SMR = df_i$SMR[1], lowestMO2 = NA, gapLimit = 4,
        max.nb.MO2.for.reg = 7)

    vaule <- o2crit$o2crit
    lowestMO2 = quantile(df_i$MO2[df_i$DO >= 80], p = 0.05)
    SMR <- o2crit$SMR
    nb_mo2_conforming <- o2crit$Nb_MO2_conforming
    r2 <- o2crit$r2
    method <- o2crit$Method
    p <- o2crit$P[1]

    pcrit_model_df <- tibble(id = id_i, pcrit_vaule = vaule, pcrit_smr = SMR, pcrit_lowestMO2 = lowestMO2,
        pcrit_nb_mo2_conforming = nb_mo2_conforming, pcrit_r2 = r2, pcrit_method = method,
        pcrit_p = p)

    pcrit_model_df_list[[id_i]] <- pcrit_model_df

    pcrit_models[[id_i]] <- o2crit

}

pcrit_model_df <- bind_rows(pcrit_model_df_list)

Ploting Pcrit

Here’s the plots for the Pcrit estimates

# Create output directory if needed
output_fig_pcrit_chabot_wd <- file.path(output_fig_wd, "model_chabot")
if (!dir.exists(output_fig_pcrit_chabot_wd)) {
    dir.create(output_fig_pcrit_chabot_wd)
}

ids <- check_pcrit_df %>%
    dplyr::distinct(id) %>%
    dplyr::pull()

pcrit_chabot_list <- list()

# Open a single PDF device
pdf(file = file.path(output_fig_pcrit_chabot_wd, "combined_chabot_plots.pdf"), width = 8,
    height = 6)

for (id_i in ids) {

    r2 <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_r2 = round(pcrit_r2, 3)) %>%
        dplyr::pull(pcrit_r2)

    conforming <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_nb_mo2_conforming = round(pcrit_nb_mo2_conforming, 3)) %>%
        dplyr::pull(pcrit_nb_mo2_conforming)

    P <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_p = round(pcrit_p, 3)) %>%
        dplyr::pull(pcrit_p)

    SMR <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_smr = round(pcrit_smr, 3)) %>%
        dplyr::pull(pcrit_smr)

    lowestMO2 <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_lowestMO2 = round(pcrit_lowestMO2, 3)) %>%
        dplyr::pull(pcrit_lowestMO2)

    # Generate and render the plot
    plotO2crit(o2critobj = pcrit_models[[id_i]])

    # Add a title
    mtext(text = paste0(id_i), side = 3, line = 2, adj = 0, col = "blue", font = 2,
        cex = 1.2)

    mtext(text = paste0("R2 = ", r2, "; p = ", P, "; CP < SMR = ", conforming, "; SMR = ",
        SMR, "; lowestMO2 = ", lowestMO2), side = 3, line = 1, adj = 0, col = "blue",
        font = 1, cex = 0.8)
}

# Close the PDF device *after* the loop
dev.off()
## png 
##   2

Printing in htlm document

ids <- check_pcrit_df %>%
    dplyr::distinct(id) %>%
    dplyr::pull()

for (id_i in ids) {

    comment <- check_pcrit_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::slice(1) %>%
        dplyr::mutate(comment = if_else(is.na(comments), "", paste0("#", comments))) %>%
        pull(comment)

    r2 <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_r2 = round(pcrit_r2, 3)) %>%
        dplyr::pull(pcrit_r2)

    conforming <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_nb_mo2_conforming = round(pcrit_nb_mo2_conforming, 3)) %>%
        dplyr::pull(pcrit_nb_mo2_conforming)

    P <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_p = round(pcrit_p, 3)) %>%
        dplyr::pull(pcrit_p)

    SMR <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_smr = round(pcrit_smr, 3)) %>%
        dplyr::pull(pcrit_smr)

    lowestMO2 <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_lowestMO2 = round(pcrit_lowestMO2, 3)) %>%
        dplyr::pull(pcrit_lowestMO2)

    # Generate and render the plot
    plotO2crit(o2critobj = pcrit_models[[id_i]])

    # Add a title
    mtext(text = paste0(id_i, " ", comment), side = 3, line = 2, adj = 0, col = "blue",
        font = 2, cex = 1.2)

    mtext(text = paste0("R2 = ", r2, "; p = ", P, "; CP < SMR = ", conforming, "; SMR = ",
        SMR, "; lowestMO2 = ", lowestMO2), side = 3, line = 1, adj = 0, col = "blue",
        font = 1, cex = 0.8)
}

Pcrit rules

We need to set some rules as to when the Pcrit estimates are reliable, as it seems many of our fish do not reach a Pcrit.

We can filter for only cases were at the lowest O2 value three consecutive MO2 measures full below our SMR and fifth percentile of the MO2 values observed at dissolved O2 levels > 80%. In the model output these are called nb_mo2_conforming points. We can the visually inspect these to see if a Pcrit is present.

pcrit_list <- pcrit_model_df %>%
    dplyr::filter(pcrit_nb_mo2_conforming > 2) %>%
    pull(id)

paste0("Based on this rule there are ", length(pcrit_list), " fish with possible Pcrits.")
## [1] "Based on this rule there are 14 fish with possible Pcrits."

Here are the plots of these 13 fish for visual confirmation

for (id_i in pcrit_list) {

    comment <- check_pcrit_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::slice(1) %>%
        dplyr::mutate(comment = if_else(is.na(comments), "", paste0("#", comments))) %>%
        pull(comment)

    r2 <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_r2 = round(pcrit_r2, 3)) %>%
        dplyr::pull(pcrit_r2)

    conforming <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_nb_mo2_conforming = round(pcrit_nb_mo2_conforming, 3)) %>%
        dplyr::pull(pcrit_nb_mo2_conforming)

    P <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_p = round(pcrit_p, 3)) %>%
        dplyr::pull(pcrit_p)

    SMR <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_smr = round(pcrit_smr, 3)) %>%
        dplyr::pull(pcrit_smr)

    lowestMO2 <- pcrit_model_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_lowestMO2 = round(pcrit_lowestMO2, 3)) %>%
        dplyr::pull(pcrit_lowestMO2)

    # Generate and render the plot
    plotO2crit(o2critobj = pcrit_models[[id_i]])

    # Add a title
    mtext(text = paste0(id_i, " ", comment), side = 3, line = 2, adj = 0, col = "blue",
        font = 2, cex = 1.2)

    mtext(text = paste0("R2 = ", r2, "; p = ", P, "; CP < SMR = ", conforming, "; SMR = ",
        SMR, "; lowestMO2 = ", lowestMO2), side = 3, line = 1, adj = 0, col = "blue",
        font = 1, cex = 0.8)
}

Pcrits numbers

Based on visual checks the following fish do have clear Pcrit values

do_have_pcrit <- c("a_9_21nov_3", "b_0_24nov_1", "b_0_24nov_2", "b_0_25nov_1", "b_0_25nov_3",
    "b_0_26_nov_1", "b_9_21_nov_1", "b_9_21nov_2", "b_9_21nov_3", "d_0_21nov_3")

n_pcrit <- length(do_have_pcrit)

have_pcirt <- pcrit_model_df %>%
    dplyr::filter(id %in% do_have_pcrit)

mean_pcrit <- have_pcirt %>%
    dplyr::reframe(mean = mean(pcrit_vaule)) %>%
    pull(mean) %>%
    round(., 2)

min_pcrit <- have_pcirt %>%
    dplyr::reframe(min = min(pcrit_vaule)) %>%
    pull(min) %>%
    round(., 2)

max_pcrit <- have_pcirt %>%
    dplyr::reframe(max = max(pcrit_vaule)) %>%
    pull(max) %>%
    round(., 2)

print(paste0("There are ", n_pcrit, " fish with identified Pcrits and the mean Pcrit is ",
    mean_pcrit, " (range: ", min_pcrit, "–", max_pcrit, ")"))
## [1] "There are 10 fish with identified Pcrits and the mean Pcrit is 24.84 (range: 17.1–32.8)"

Additional code used to validate approach

Frequentist weighted regressions

We will use weighted regression to account for a higher density of data at normoxic conditions (i.e. SMR values). I have used two different weighting approaches, (1) weighting the importance of each data point based on the frequency of points in a given o2 space (12 evenly spaced bins), or (2) weighting only the SMR slopes, as they are the only values that have repeated measures. Points that have higher weights influence the model fit more, while points with lower weights have less impact. A high density of points at high o2 values could lead to overfitting in that region, while underfitting or misrepresenting trends in lower-density regions (e.g., low o2 vaules).

Here we are making the two weightings. The first is achieved by spiting the o2 data in 12 evenly spaced bins, and summing the number of data points in that bin, the weight is then given to data points within that bin based on the inverse frequency of data points.The second methods, is simply applied to the SMR measurements, and is the inverse frequency of SMR measurements.

slope_tidy <- slope_tidy %>%
  dplyr::group_by(id) %>% 
  dplyr::mutate(o2_bin = cut(DO, breaks = 12),
                weight_smr = if_else(phase == "smr", 1/sum(phase == "smr"), 1)) %>% 
  ungroup() %>%
  dplyr::group_by(id, o2_bin) %>% 
  dplyr::mutate(
    bin_freq = length(order),          # Count points in each bin
    weight_bins = 1 / bin_freq           # Weight = inverse frequency
      ) %>% 
  ungroup()

Here we are building the weighted regressions.

ids <- slope_tidy %>%
  dplyr::distinct(id) %>%
  pull(id) %>%
  as.list()

weighted_model_comparison_list <- list()

weighted_model_results_list <- list()

for (id_i in ids) {
  
  # Filter data for the current ID
  df_i <- slope_tidy %>%
    dplyr::filter(id == id_i)
  
  
  # Fit models with weights
  models <- list(
    lm_0 = lm(MO2_g ~ 1, data = df_i, weights = weight_smr),                # 0th-order (constant mean)
    lm_1 = lm(MO2_g ~ DO, data = df_i, weights = weight_smr),               # 1st-order (linear)
    lm_2 = lm(MO2_g ~ poly(DO, 2), data = df_i, weights = weight_smr),      # 2nd-order (quadratic)
    lm_3 = lm(MO2_g ~ poly(DO, 3), data = df_i, weights = weight_smr)       # 3rd-order (cubic)
  )
  
  # Extract metrics to compare models
  weighted_model_comparison_list[[id_i]] <- purrr::map_df(models, glance, .id = "model") %>%
    dplyr::mutate(id = id_i) %>%
    dplyr::select(id, everything())
  
  weighted_model_results_list[[id_i]] <- map_df(models, ~ tidy(.x, conf.int = TRUE), .id = "model") %>%
    clean_names() %>% 
    mutate(id = id_i) %>%
    select(id, everything())
}

# Combine dataframes into a single data frame
weighted_model_comparison <- bind_rows(weighted_model_comparison_list) %>%
  dplyr::mutate(poly = as.numeric(stringr::str_remove_all(model, "lm_")))

weighted_model_results <- bind_rows(weighted_model_results_list) %>%
  dplyr::mutate(poly = as.numeric(stringr::str_remove_all(model, "lm_")))

Selecting the best fitting model

best_weighted_model <- weighted_model_comparison %>% 
  dplyr::group_by(id) %>%
  dplyr::mutate(
    AIC_rank = rank(-AIC),  # Rank descending for AIC
    BIC_rank = rank(-BIC),  # Rank descending for BIC
    logLik_rank = rank(logLik),  # Rank ascending for logLik
    score = AIC_rank + BIC_rank + logLik_rank  # Combined score
  ) %>%
  dplyr::arrange(desc(score)) %>%  # Arrange by descending score
  dplyr::slice(1)

Now we are plotting each of the regressions. First making a directory to save the figures

incremental_reg_freq_wd <- file.path(output_fig_wd, "incremental_regressions./freq")
if (!dir.exists(incremental_reg_freq_wd)) {
    dir.create(incremental_reg_freq_wd)
}

Ploting all regression, and highlighting the model that has the best fit, based on AIC values

# Create a list to store the plots
plots <- list()
model_preds_list <- list()

for (id_i in ids) {
  
  # Filter data for the current ID
  df_i <- slope_tidy %>%
    filter(id == id_i)
  
  x_min <- df_i %>%
    dplyr::reframe(min = min(DO), na.rm = TRUE) %>% 
    dplyr::pull(min)
  
  y_max <- df_i %>%
    dplyr::reframe(max = max(MO2_g), na.rm = TRUE) %>% 
    dplyr::pull(max)
  
  # Get model predictions
  df_predictions <- data.frame(DO = seq(min(df_i$DO), max(df_i$DO), length.out = 100))
  df_predictions$id <- id_i
  

  # Generate predictions for each model
  models <- list(
    lm_0 = lm(MO2_g ~ 1, data = df_i, weights = df_i$weight_smr),
    lm_1 = lm(MO2_g ~ DO, data = df_i, weights = df_i$weight_smr),
    lm_2 = lm(MO2_g ~ poly(DO, 2), data = df_i, weights = df_i$weight_smr),
    lm_3 = lm(MO2_g ~ poly(DO, 3), data = df_i, weights = df_i$weight_smr)
  )
  
  best_weighted_model_i <- best_weighted_model %>% 
    dplyr::filter(id == id_i)
  
  poly_i_name <- best_weighted_model_i %>%
    dplyr::mutate(name = case_when(
      poly == 0 ~ "0th-order polynomial",
      poly == 1 ~ "1st-order polynomial",
      poly == 2 ~ "2nd-order polynomial",
      poly == 3 ~ "3rd-order polynomial",
      TRUE ~ "ERROR"
    )) %>% 
    dplyr::pull(name)
  
  r_i <- best_weighted_model_i %>% 
    dplyr::pull(r.squared) %>% 
    round(., 3)
  
  p_value_i <- best_weighted_model_i %>% 
    dplyr::pull(p.value) %>% 
    round(., 3)
  
  p_value_i <- if_else(p_value_i < 0.001, "< 0.001", paste0("= ", p_value_i))
  
  model_i <- best_weighted_model_i %>% 
    dplyr::pull(model)
  
  # Add predictions to the dataframe
  for (model_name in names(models)) {
    df_predictions[[model_name]] <- predict(models[[model_name]], newdata = df_predictions)
  }
  
  # Reshape data for plotting
  df_predictions_long <- df_predictions %>%
    pivot_longer(cols = starts_with("lm_"), names_to = "model", values_to = "MO2_pred") %>%
    mutate(line_size = if_else(model == model_i, 2, 1),
           alpha_value = if_else(model == model_i, 1, 0.4))
  
  # Create the plot
  p <- ggplot() +
    geom_point(data = df_i, aes(x = DO, y = MO2_g), alpha = 0.6, colour = "black", size = 2) +
    geom_line(data = df_predictions_long, 
              aes(x = DO, y = MO2_pred, colour = model, size = line_size, alpha = alpha_value)) +
    scale_colour_manual(values = c("red", "blue", "green", "purple"), 
                        labels = c("0th Order", "1st Order", "2nd Order", "3rd Order")) +
    scale_size_identity() +  # Use the size values directly
    scale_alpha_identity(guide = "none") +  # Remove the alpha legend 
    annotate("text", x = x_min, 
             y = y_max, 
             label = paste0("Best fit: ",poly_i_name, "\n", "R = ", r_i, "; P ", p_value_i), 
             hjust = 0, vjust = 1, size = 4) +
    labs(
      title = paste("Model Fits vs Raw Data for ID", id_i),
      x = "Dissolved oxygen percentage (DO)",
      y = "MO2 (o2 mg/g/h)",
      colour = "Model") +
    theme_classic()
  
  # Store the plot
  plots[[id_i]] <- p
  model_preds_list[[id_i]] <- df_predictions_long
  
  print(p)
}

#To save all plots to individual files
for (id_i in ids) {
  ggsave(filename = paste0(incremental_reg_freq_wd, "./plot_", id_i, ".png"), plot = plots[[id_i]], width = 8, height = 6)
}

model_preds_df <- bind_rows(model_preds_list)

The best fitting models were most often a 3rd-order polynomial (n = 27, 46.55%) or a 2nd-order polynomial (n = 19, 32.76%). This could suggest the presence of a critical oxygen threshold (Pcrit) where the relationship between o2 and MO2 changes. To confirm their is a Pcrit, we need to validated the shape of the polynomials and in should use a more specific model to test the Pcrit value. This type of model is indicative of oxyregulator.

The next most common are 0th-order and 1st-order polynomials (both n = 6, 10.34%). In the case of the 0th-order model, it suggests that MO2 does not show a statistically significant dependence on the o2. In other words, the metabolic rate does not adjust based on oxygen availability, and there is no clear critical oxygen threshold (Pcrit) where the relationship changes. This is indicative of a oxyregulator. In the case of the 1st-order polynomials, it suggest the presences of linear relationship between o2 and MO2, which is indicative of oxyconformer. However, to be true evidence of a oxyconformer this relationship should be positive (i.e. as o2 falls MO2 also falls). Only 3 of the 6 individuals best modelled with a linear function were positive, and only 2 were statistically significant (Table 1).

total_fish <- nrow(best_weighted_model)

table_bwm <- best_weighted_model %>%
    dplyr::group_by(poly) %>%
    dplyr::reframe(n = length(id), percent = round((n/total_fish) * 100, 2)) %>%
    dplyr::mutate(best_model_name = case_when(poly == 0 ~ "0th-order polynomial",
        poly == 1 ~ "1st-order polynomial", poly == 2 ~ "2nd-order polynomial", poly ==
            3 ~ "3rd-order polynomial", TRUE ~ "ERROR")) %>%
    dplyr::select(best_model_name, everything(), -poly)


table_bwm %>%
    gt() %>%
    cols_align(align = "center", columns = everything())
best_model_name n percent
0th-order polynomial 6 10.34
1st-order polynomial 6 10.34
2nd-order polynomial 19 32.76
3rd-order polynomial 27 46.55

Adding model information to the full data frame

slope_tidy <- full_join(slope_tidy, best_weighted_model, by = "id") %>%
    dplyr::rename(model_freq = model, r_squared_freq = r.squared, adj_r_squared_freq = adj.r.squared,
        sigma_freq = sigma) %>%
    dplyr::mutate(best_model_name = case_when(poly == 0 ~ "0th-order polynomial",
        poly == 1 ~ "1st-order polynomial", poly == 2 ~ "2nd-order polynomial", poly ==
            3 ~ "3rd-order polynomial", TRUE ~ "ERROR"))

Here we are grouping fish by best fitting model and getting an average trend

global_models <- list(
    lm_0 = lmer(MO2_g ~ 1 + (1|id), data = slope_tidy %>% 
                  dplyr::filter(poly == 0), weights = weight_smr),
    lm_1 = lmer(MO2_g ~ DO + (1|id), data = slope_tidy %>% 
                  dplyr::filter(poly == 1), weights = weight_smr),
    lm_2 = lmer(MO2_g ~ poly(DO, 2) + (1|id), data = slope_tidy %>% 
                  dplyr::filter(poly == 2), weights = weight_smr),
    lm_3 = lmer(MO2_g ~ poly(DO, 3) + (1|id), data = slope_tidy %>% 
                  dplyr::filter(poly == 3), weights = weight_smr)
  )


global_predictions <- data.frame(DO = seq(min(slope_tidy$DO), max(slope_tidy$DO), length.out = 100))

for (model_name in names(global_models)) {
  predictions <- predict(
    global_models[[model_name]], 
    newdata = global_predictions, 
    re.form = NA,  # Excludes random effects (population-level predictions)
    se.fit = TRUE  # Returns standard errors
  )
  
  global_predictions[[paste0(model_name, "_fit")]] <- predictions$fit
  global_predictions[[paste0(model_name, "_lwr")]] <- predictions$fit - 1.96 * predictions$se.fit
  global_predictions[[paste0(model_name, "_upr")]] <- predictions$fit + 1.96 * predictions$se.fit
}

global_predictions_long <- global_predictions %>%
  pivot_longer(
    cols = matches("lm_.*_fit|lm_.*_lwr|lm_.*_upr"),
    names_to = c("model", ".value"),
    names_pattern = "(lm_\\d+)_(.*)"
  ) %>%
  dplyr::mutate(best_model_name = case_when(
    model == "lm_0" ~ "0th-order polynomial",
    model == "lm_1" ~ "1st-order polynomial",
    model == "lm_2" ~ "2nd-order polynomial",
    model == "lm_3" ~ "3rd-order polynomial",
    TRUE ~ "ERROR"
  ))

Figure

best_weighted_model_pred <- best_weighted_model %>% 
  dplyr::left_join(., model_preds_df, by = c("id", "model")) %>% 
  dplyr::ungroup() %>% 
  dplyr::mutate(best_model_name = case_when(
      poly == 0 ~ "0th-order polynomial",
      poly == 1 ~ "1st-order polynomial",
      poly == 2 ~ "2nd-order polynomial",
      poly == 3 ~ "3rd-order polynomial",
      TRUE ~ "ERROR"
    ))

annotation_data <- table_bwm %>%
  dplyr::select(best_model_name, n) 

fig_1 <- ggplot() +
  geom_line(data = best_weighted_model_pred, 
            aes(x = DO, y = MO2_pred, color = id), size = 1, alpha = 1) +
  geom_point(data = slope_tidy, aes(x = DO, y = MO2_g), alpha = 0.1, colour = "black", size = 2) +
  geom_ribbon(data = global_predictions_long, 
              aes(x = DO, ymin = lwr, ymax = upr, group = model), 
              fill = "#FC6C85", alpha = 0.2) +  # Shaded confidence intervals
  geom_line(data = global_predictions_long, 
            aes(x = DO, y = fit), size = 1.5, color = "#FF007F") +
  facet_wrap(~best_model_name) +
  scale_color_grey(start = 0.1, end = 0.9) +
  labs(
      title = paste("Model estimates and observed data grouped by best fitting model"),
      x = "Dissolved oxygen percentage (DO)",
      y = "MO2 (O2 mg/g/h)") +
  theme_classic() +
  theme(legend.position = "none") +
  geom_text(data = annotation_data, 
            aes(x = -Inf, y = Inf, label = paste0("italic(n) == ", n)), 
            hjust = -0.1, vjust = 1.2, inherit.aes = FALSE, parse = TRUE)
fig_1


For the 6 fish that had o2 and MO2 relationships best modelled with a linear function, only 6 are were positive relationships (which we would expect if fish were oxyconforming), and none are statistically significant.

ids_lm_1_list <- best_weighted_model %>%
    dplyr::filter(model == "lm_1") %>%
    dplyr::pull(id)

weighted_model_results %>%
    dplyr::filter(id %in% ids_lm_1_list & model == "lm_1" & term == "DO") %>%
    dplyr::mutate(estimate = round(estimate, 5), ci = paste0(round(conf_low, 5),
        " – ", round(conf_high, 5)), p_value = round(p_value, 3)) %>%
    dplyr::select(id, estimate, ci, p_value) %>%
    gt() %>%
    cols_align(align = "center", columns = everything())
id estimate ci p_value
a_9_22nov_1 0.00061 0.00012 – 0.0011 0.016
b_0_25nov_2 0.00021 -5e-05 – 0.00046 0.104
b_0_26nov_3 0.00023 3e-05 – 0.00042 0.025
b_0_27nov_2 -0.00016 -0.00037 – 4e-05 0.111
c_9_26nov_3 -0.00095 -0.00139 – -0.00051 0.000
d_9_25nov_2 -0.00132 -0.00215 – -0.00049 0.003

Caculating Pcrit with Chabot SMR

Here we build the same models as above but using the SMR estimated with the Chabot methods.

ids <- check_pcrit_df %>%
    dplyr::distinct(id) %>%
    dplyr::pull()

pcrit_model_df_list_2 <- list()
pcrit_models_2 <- list()

for (id_i in ids) {

    df_i <- check_pcrit_df %>%
        dplyr::filter(id == id_i)

    o2crit <- calcO2crit(Data = df_i, SMR = df_i$SMR_CHABOT[1], lowestMO2 = NA, gapLimit = 4,
        max.nb.MO2.for.reg = 7)

    lowestMO2 = quantile(df_i$MO2[df_i$DO >= 80], p = 0.05)
    vaule <- o2crit$o2crit
    SMR <- o2crit$SMR
    nb_mo2_conforming <- o2crit$Nb_MO2_conforming
    r2 <- o2crit$r2
    method <- o2crit$Method
    p <- o2crit$P[1]

    pcrit_model_df <- tibble(id = id_i, pcrit_vaule = vaule, pcrit_SMR = SMR, pcrit_lowestMO2 = lowestMO2,
        pcrit_nb_mo2_conforming = nb_mo2_conforming, pcrit_r2 = r2, pcrit_method = method,
        pcrit_p = p)

    pcrit_model_df_list_2[[id_i]] <- pcrit_model_df

    pcrit_models_2[[id_i]] <- o2crit

}

pcrit_model_df_2 <- bind_rows(pcrit_model_df_list_2)

Now filtering out based on the same rules above

pcrit_list_2 <- pcrit_model_df_2 %>%
    dplyr::filter(pcrit_nb_mo2_conforming > 2) %>%
    pull(id)

paste0("Based on this rule there are ", length(pcrit_list_2), " fish with possible Pcrits.")
## [1] "Based on this rule there are 14 fish with possible Pcrits."

Plotting with the SMR Chabot method

for (id_i in pcrit_list) {

    comment <- check_pcrit_df %>%
        dplyr::filter(id == id_i) %>%
        dplyr::slice(1) %>%
        dplyr::mutate(comment = if_else(is.na(comments), "", paste0("#", comments))) %>%
        pull(comment)

    r2 <- pcrit_model_df_2 %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_r2 = round(pcrit_r2, 3)) %>%
        dplyr::pull(pcrit_r2)

    conforming <- pcrit_model_df_2 %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_nb_mo2_conforming = round(pcrit_nb_mo2_conforming, 3)) %>%
        dplyr::pull(pcrit_nb_mo2_conforming)

    P <- pcrit_model_df_2 %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_p = round(pcrit_p, 3)) %>%
        dplyr::pull(pcrit_p)

    SMR <- pcrit_model_df_2 %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_SMR = round(pcrit_SMR, 3)) %>%
        dplyr::pull(pcrit_SMR)

    lowestMO2 <- pcrit_model_df_2 %>%
        dplyr::filter(id == id_i) %>%
        dplyr::mutate(pcrit_lowestMO2 = round(pcrit_lowestMO2, 3)) %>%
        dplyr::pull(pcrit_lowestMO2)

    # Generate and render the plot
    plotO2crit(o2critobj = pcrit_models_2[[id_i]])

    # Add a title
    mtext(text = paste0(id_i, " ", comment), side = 3, line = 2, adj = 0, col = "blue",
        font = 2, cex = 1.2)

    mtext(text = paste0("R2 = ", r2, "; p = ", P, "; CP < SMR = ", conforming, "; SMR = ",
        SMR, "; lowestMO2 = ", lowestMO2), side = 3, line = 1, adj = 0, col = "blue",
        font = 1, cex = 0.8)
}

have_pcirt_2 <- c("a_9_21nov_3", "b_0_24_nov_1", "b_0_24nov_2", "b_0_25nov_1", "b_0_25nov_3",
    "b_0_26_1", "b_9_21nov_1", "b_9_21nov_2", "b_9_21nov_3", "d_0_21nov_3")

length(have_pcirt_2)
## [1] 10

Pcrits numbers

Based on visual checks the following fish do have clear Pcrit values

do_have_pcirt_2 <- c("a_9_21nov_3", "b_0_24_nov_1", "b_0_24nov_2", "b_0_25nov_1",
    "b_0_25nov_3", "b_0_26_1", "b_9_21nov_1", "b_9_21nov_2", "b_9_21nov_3", "d_0_21nov_3")

n_pcrit <- length(do_have_pcirt_2)

have_pcirt_2 <- pcrit_model_df_2 %>%
    dplyr::filter(id %in% do_have_pcirt_2)

mean_pcrit <- have_pcirt_2 %>%
    dplyr::reframe(mean = mean(pcrit_vaule)) %>%
    pull(mean) %>%
    round(., 2)

min_pcrit <- have_pcirt_2 %>%
    dplyr::reframe(min = min(pcrit_vaule)) %>%
    pull(min) %>%
    round(., 2)

max_pcrit <- have_pcirt_2 %>%
    dplyr::reframe(max = max(pcrit_vaule)) %>%
    pull(max) %>%
    round(., 2)

print(paste0("There are ", n_pcrit, " fish with identified Pcrits and the mean Pcrit is ",
    mean_pcrit, " (range: ", min_pcrit, "–", max_pcrit, ")"))
## [1] "There are 10 fish with identified Pcrits and the mean Pcrit is 26.92 (range: 17.9–39.9)"

Other techniques for Pcrit calculation

Here using the the fish that were detriment to have Pcrits, we will also estimate Pcrit with five popular techniques for Pcrit calculation: the traditional breakpoint metric (broken stick regression), the nonlinear regression metric (Marshall et al. 2013), the sub-prediction interval metric (Birk et al. 2019), the alpha-based Pcrit method (Seibel et al. 2021), and the linear low O2 (LLO) method (Reemeyer & Rees 2019).

The function is called calc_pcrit() and is part of the respirometry package.

Link: https://search.r-project.org/CRAN/refmans/respirometry/html/calc_pcrit.html

Model parameters

Parameters to consider

  • avg_top_n: for alpha method, a numeric value representing the number of top α0 (MO2/PO2) values to average together to estimate α. Default is 1. We recommend no more than 3 to avoid diminishing the α value with sub-maximal observations.

  • level: for Sub_PI method, Percentage at which the prediction interval should be constructed.

  • iqr: Only for Sub_PI. Removes mo2 observations that are this many interquartile ranges away from the mean value for the oxyregulating portion of the trial. If this filtering is not desired, set to infinity.

  • NLR_m: only applies to NLR. Pcrit is defined as the PO2 at which the slope of the best fitting function equals NLR_m (after the MO2 data are normalized to the 90% quantile). Default is 0.065

  • MR: A numeric value for the metabolic rate at which pcrit_alpha and pcrit_LLO should be returned. If not supplied by the user, then the mean MO2 of the “oxyregulating” portion of the curve is applied for pcrit_alpha and NA is returned for pcrit_LLO.

  • mo2_threshold: A single numeric value above which mo2 values are ignored for alpha Pcrit estimation. Useful to removing obviously erroneous values. Default is Inf.

Formate data

We will only those that may have a Pcirt, we will also need to weight the SMR vaules some way. In this case there is not easy way to weight the models themsevles, so instead we can just take a aveage of the SMR values and use only that

pcrit_check_smr_df <- slope_tidy %>%
    dplyr::filter(id %in% pcrit_list & phase == "smr") %>%
    dplyr::group_by(id) %>%
    dplyr::reframe(DO = mean(DO, na.rm = TRUE), MO2 = SMR[1], SMR = SMR[1])

pcrit_check_closed_df <- slope_tidy %>%
    dplyr::filter(id %in% pcrit_list & phase != "smr") %>%
    dplyr::select(id, DO, MO2, SMR)

pcrit_check_df <- rbind(pcrit_check_smr_df, pcrit_check_closed_df)

pcrit_check_df_n <- pcrit_check_df %>%
    dplyr::distinct(id) %>%
    nrow(.)

paste0("n for possible Pcrit = ", pcrit_check_df_n)
## [1] "n for possible Pcrit = 14"

Here we build the models

combined_pcirt_list <- list()

for (id_i in pcrit_list) {

  id_name <- id_i

  mo2_data <- pcrit_check_df %>%
    dplyr::filter(id == id_i)

  MR_set <- mo2_data$SMR[1] %>% as.numeric()

  # Use tryCatch to handle errors and skip problematic calculations
  pcrit_df <- tryCatch({

    pcrit_df <- calc_pcrit(po2 = mo2_data$DO,
           mo2 = mo2_data$MO2,
           method = 'All',
           avg_top_n = 2, # alpha metric (default = 1) recommend no more than 3
           level = 0.95, # Sub_PI metric (default = 0.95)
           iqr = 1.5, # Sub_PI metric (default = 1.5)
           NLR_m = 0.065, # NLR metric (default = 0.065)
           MR = MR_set, # alpha and LLO metrics,
           mo2_threshold = Inf, # alpha metric
           return_models = FALSE # return model parameters?
           ) %>%
      as.data.frame() %>%
      rownames_as_column(var = "method") %>%
      rename(value = ".") %>%
      tidyr::pivot_wider(.,
                     names_from = method,
                     values_from = value) %>%
      dplyr::mutate(id = id_name) %>%
      dplyr::select(id, everything())

  }, error = function(e) {
    message("Skipping channel ", id_name, " due to error: ", conditionMessage(e))
    NULL
  })

  # Only add to list if pcrit_df is not NULL
  if (!is.null(pcrit_df)) {
    combined_pcirt_list[[id_name]] <- pcrit_df
  }
}


Combined all the Pcrit model estimates together

pcirt <- bind_rows(combined_pcirt_list)

id_s_comp <- pcirt %>%
    dplyr::pull()

Plotting Pcrit

Here we will save the plots for the various Pcrit curves.

# Create output directory if needed
output_fig_pcrit_alternative_wd <- file.path(output_fig_wd, "pcrit-alternative")
if (!dir.exists(output_fig_pcrit_alternative_wd)) {
  dir.create(output_fig_pcrit_alternative_wd)
}

# Open a single PDF device once
pdf(file = file.path(output_fig_pcrit_alternative_wd, "combined_pcrit_plots.pdf"),
    width = 8, height = 6)

for (id_i in pcrit_list) {

  id_name <- id_i

  mo2_data <- pcrit_check_df %>%
    dplyr::filter(id == id_i)

  MR_set <- mo2_data$SMR[1] %>% as.numeric()

  tryCatch({
    # Generate and render the plot
    plot_pcrit(
      po2 = mo2_data$DO,
      mo2 = mo2_data$MO2,
      method = 'All',
      avg_top_n = 1,
      level = 0.95,
      iqr = 1.5,
      NLR_m = 0.065,
      MR = MR_set,
      mo2_threshold = Inf,
      return_models = FALSE,
      showNLRs = FALSE
    )

    # Add a title in the top-left corner
    mtext(text = paste(id_name),
          side = 3, line = 2, adj = 0, # Top margin, aligned to left
          col = "blue", font = 2, cex = 1.2)

  }, error = function(e) {
    message("Skipping channel ", id_name, " due to error: ", conditionMessage(e))
  })
}

# Close the PDF device *after* the loop
dev.off()
## png 
##   2


Plotting in the html. None of the models appear to estimate a Pcrit value convincingly.

for (id_i in pcrit_list) {

  id_name <- id_i

  mo2_data <- pcrit_check_df %>%
    dplyr::filter(id == id_i)

  MR_set <- mo2_data$SMR[1] %>% as.numeric()

  tryCatch({
    # Generate and render the plot
    plot_pcrit(
      po2 = mo2_data$DO,
      mo2 = mo2_data$MO2,
      method = 'All',
      avg_top_n = 1,
      level = 0.95,
      iqr = 1.5,
      NLR_m = 0.065,
      MR = MR_set,
      mo2_threshold = Inf,
      return_models = FALSE,
      showNLRs = FALSE
    )

    # Add a title in the top-left corner
    mtext(text = paste(id_name),
          side = 3, line = 2, adj = 0, # Top margin, aligned to left
          col = "blue", font = 2, cex = 1.2)

  }, error = function(e) {
    message("Skipping channel ", id_name, " due to error: ", conditionMessage(e))
  })
}

LS0tDQp0aXRsZTogImdtYWMtbGFiLWNoYXJ0Ig0KYXV0aG9yOiAiSmFrZSBNYXJ0aW4iDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgZGVwdGg6IDQNCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vDQogICAgdGhlbWU6ICBjb3Ntbw0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQprbml0OiB8DQogIChmdW5jdGlvbihpbnB1dCwgLi4uKSB7DQogICAgcm1hcmtkb3duOjpyZW5kZXIoDQogICAgICBpbnB1dCwNCiAgICAgIG91dHB1dF9maWxlID0gcGFzdGUwKA0KICAgICAgICdpbmRleC5odG1sJw0KICAgICAgKSwNCiAgICAgIGVudmlyID0gZ2xvYmFsZW52KCkNCiAgICApDQogIH0pDQotLS0NCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBSRUFEIE1FIA0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQoqKlNVTU1BUlkqKiA8YnI+DQpUaGlzIFIgY29kZSBpcyB1c2VkIHRvIGVzdGltYXRlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBveHlnZW4gY29uc3VtcHRpb24gKE1PMikgYW5kIGFtYmllbnQgb3h5Z2VuIHBhcnRpYWwgcHJlc3N1cmUgKFBPMikgaW4gdGhlIENvbW1vbiBnYWxheGlhcyAoKkdhbGF4aWFzIG1hY3VsYXR1cyopLiBJdCBpcyBhbHNvIHVzZWQgdG8gZXN0aW1hdGUgdGhlIGNyaXRpY2FsIHBhcnRpYWwgcHJlc3N1cmUgb2Ygb3h5Z2VuIGZvciBhZXJvYmljIG1ldGFib2xpc20gKFBjcml0KSwgd2hpY2ggaXMgY29tbW9ubHkgdW5kZXJzdG9vZCBhcyB0aGUgdGhyZXNob2xkIGJlbG93IHdoaWNoIG94eWdlbiBjb25zdW1wdGlvbiByYXRlIGNhbiBubyBsb25nZXIgYmUgc3VzdGFpbmVkLiBUaGUgYXNzb2NpYXRlZCBhcnRpY2xlIGlzICJUaGUgcm9sZSBvZiBvc21vcmVzcGlyYXRvcnkgY29tcHJvbWlzZSBpbiBoeXBveGlhIHRvbGVyYW5jZSBvZiB0aGUgcHVycG9ydGVkbHkgb3h5Y29uZm9ybWluZyB0ZWxlb3N0ICpHYWxheGlhcyBtYWN1bGF0dXMqIi4gPGJyPiANCg0KKipBSU0qKg0KVGhlIGFydGljbGUgYWltcyB0byB0ZXN0IHdoZXRoZXIgKkdhbGF4aWFzIG1hY3VsYXR1cyogY2FuIG1haW50YWluIG94eWdlbiBjb25zdW1wdGlvbiAoTU8yKSBhcyBhbWJpZW50IFBPMiBmYWxscywgYW5kIGlmIHNvLCBhdCB3aGF0IGxldmVsIGl0IHJlYWNoZXMgY3JpdGljYWwgcGFydGlhbCBwcmVzc3VyZSBvZiBveHlnZW4gZm9yIGFlcm9iaWMgbWV0YWJvbGlzbSAoUGNyaXQpLg0KDQoqKkFVVEhPUlMqKjxicj4NClRvIGJlIGFkZGVkDQo8YnI+DQoNCioqQUZGSUxJQVRJT05TKiogPGJyPg0KVG8gYmUgYWRkZWQNCjxicj4NCg0KKipBSU0qKiA8YnI+DQpUbyBiZSBhZGRlZA0KPGJyPg0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIEtuaXQgc2V0dGluZ3MgDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNClRoZXNlIGFyZSB0aGUgc2V0dGluZ3MgZm9yIHRoZSBodG1sIG91dHB1dC4gV2Ugd2lsbCB1c2UgdGhpcyB0byBtYWtlIG91dCBpbmRleCBmaWxlIG9uIEdpdA0KDQpgYGB7ciBzZXR1cH0NCiNrbml0ZXIgc2VldHRpbmcNCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCm1lc3NhZ2UgPSBGQUxTRSwNCndhcm5pbmcgPSBGQUxTRSwgIyBubyB3YXJuaW5ncw0KY2FjaGUgPSBUUlVFLCMgQ2FjaGVpbmcgdG8gc2F2ZSB0aW1lIHdoZW4ga25pdGluZw0KdGlkeSA9IFRSVUUNCikNCmBgYA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIFNjcmlwdCBjb250YWN0DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCioqSmFrZSBNLiBNYXJ0aW4qKiA8YnI+DQoNCioqRW1haWwqKjogamFrZS5tYXJ0aW5AZGVha2luLmVkdS5hdSAob3IgamFrZS5tYXJ0aW4ucmVzZWFyY2hAZ21haWwuY29tKSA8YnI+DQoNCioqV2ViKio6ICBodHRwczovL2pha2UubWFydGluLm9yZyA8YnI+DQoNCioqR2l0SHViKio6IGh0dHBzOi8vZ2l0aHViLmNvbS9KYWtlTWFydGluUmVzZWFyY2ggPGJyPg0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIFJlcXVpcmVkIHBhY2thZ2VzDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNClRoZXNlIGFyZSB0aGUgUiBwYWNrYWdlcyByZXF1aXJlZCBmb3IgdGhpcyBzY3JpcHQuIFlvdSB3aWxsIG5lZWQgdG8gaW5zdGFsbCBhIHBhY2thZ2UgY2FsbGVkIHBhY21hbiB0byBydW4gdGhlIHBfbG9hZCBmdW5jdGlvbi4gDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCiMgdGhpcyBpbnN0YWxscyBhbmQgbG9hZCBwYWNrYWdlcw0KIyBuZWVkIHRvIGluc3RhbGwgcGFjbWFuDQpwYWNtYW46OnBfbG9hZCgiZ2dwbG90MiIsIA0KICAgICAgICAgICAgICAgImdndGhlbWVzIiwgDQogICAgICAgICAgICAgICAiZ2dmb3J0aWZ5IiwgDQogICAgICAgICAgICAgICAiZ3RFeHRyYXMiLCANCiAgICAgICAgICAgICAgICJpZ3JhcGgiLA0KICAgICAgICAgICAgICAgImRhZ2l0dHkiLA0KICAgICAgICAgICAgICAgImdnZGFnIiwNCiAgICAgICAgICAgICAgICJnZ3JpZGdlcyIsDQogICAgICAgICAgICAgICAiZ2doYWx2ZXMiLA0KICAgICAgICAgICAgICAgImdnRXh0cmEiLA0KICAgICAgICAgICAgICAgImdyaWRFeHRyYSIsDQogICAgICAgICAgICAgICAiY29ycnBsb3QiLA0KICAgICAgICAgICAgICAgIlJDb2xvckJyZXdlciIsIA0KICAgICAgICAgICAgICAgImd0IiwgDQogICAgICAgICAgICAgICAiZ3RzdW1tYXJ5IiwNCiAgICAgICAgICAgICAgICJncmlkIiwNCiAgICAgICAgICAgICAgICJwbG90bHkiLCAjIGRhdGEgdmlzdWFsaXNhdGlvbg0KICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgInRpZHl2ZXJzZSIsIA0KICAgICAgICAgICAgICAgImphbml0b3IiLCANCiAgICAgICAgICAgICAgICJyZWFkeGwiLCANCiAgICAgICAgICAgICAgICJicm9vbS5taXhlZCIsIA0KICAgICAgICAgICAgICAgImRhdGEudGFibGUiLCANCiAgICAgICAgICAgICAgICJkZXZ0b29scyIsDQogICAgICAgICAgICAgICAiaG1zIiwNCiAgICAgICAgICAgICAgICJyb3VuZF9obXMiLCAjIGRhdGEgdGlkeQ0KICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAibWFyZ2luYWxlZmZlY3RzIiwgDQogICAgICAgICAgICAgICAiYnJtcyIsIA0KICAgICAgICAgICAgICAgInJzdGFuIiwgDQogICAgICAgICAgICAgICAicGVyZm9ybWFuY2UiLCANCiAgICAgICAgICAgICAgICJlbW1lYW5zIiwgDQogICAgICAgICAgICAgICAidGlkeWJheWVzIiwgDQogICAgICAgICAgICAgICAidmVnYW4iLA0KICAgICAgICAgICAgICAgImJldGFyZWciLA0KICAgICAgICAgICAgICAgImxtZTQiLCANCiAgICAgICAgICAgICAgICJjYXIiLCANCiAgICAgICAgICAgICAgICJsbWVyVGVzdCIsDQogICAgICAgICAgICAgICAicXFwbG90ciIsDQogICAgICAgICAgICAgICAicmVzcGlyb21ldHJ5IiwNCiAgICAgICAgICAgICAgICJtY2x1c3QiLA0KICAgICAgICAgICAgICAgImZ1cnJyIiwNCiAgICAgICAgICAgICAgICMgbW9kZWxsaW5nIA0KICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgImRhdGF3aXphcmQiLCANCiAgICAgICAgICAgICAgICJTUlMiICMgZGF0YSBtYW5pcHVsYXRpb24gDQogICAgICAgICAgICAgICAgICAgICAgICkNCmBgYA0KDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIEZ1bmN0aW9ucyAoY3VzdG9tKQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KSGVyZSBhcmUgc29tZSBjdXN0b20gZnVuY3Rpb24gdXNlZCB3aXRoaW4gdGhpcyBzY3JpcHQuIDxicj4gDQoNCioqYmF5ZXNfaW5jcmVtZW50YWxfcmVncmVzc2lvbl9ieV9pZCoqOiBBIGN1c3RvbSBmdW5jdGlvbiB0byBidWlsZCBCYXllc2lhbiBpbmNyZW1lbnRhbCByZWdyZXNzaW9ucyBpbiBwYXJhbGxlbCA8YnI+DQoNCiMjIE5vdGU6IGFkZCBjYXNlIHdoZXJlIGlkID0gTlVMTA0KDQpgYGB7cn0NCiMgSW5zdGVhZCB3ZSBjb3VsZCBhbHNvIHVzZSBhIGRpc3RyaWJ1dGlvbmFsIHJlZ3Jlc3Npb24gYXBwcm9hY2gsIGJ5IHNwZWNpZmljYWxseSBtb2RlbGxpbmcgdGhlIHZhcmlhbmNlIGJ5IERPIChlLmcuIHNpZ21hIH4gRE8pLiBXZWlnaHRpbmcgbWF5IG5vdCBiZSByZXF1aXJlZCBpbiB0aGlzIGNhc2UsIEkgZG9uJ3QgdGhpbmsgaGlnaGVyIGRlbnNpdHkgb2YgdmF1bGVzIGluIGEgZ2l2ZW4gc3BhY2Ugd2lsbCBlZmZlY3QgQmF5ZXNpYW4gZXN0aW1hdGVzIGxpa2UgaXQgZG9lcyBpbiBmcmVxdWVudGlzdCBtb2RlbHMuIFNlZSBkaXNjb3Vyc2UgaHR0cHM6Ly9kaXNjb3Vyc2UubWMtc3Rhbi5vcmcvdC93ZWlnaHRzLWluLWJybS80Mjc4IDxicj4NCg0KYmF5ZXNfaW5jcmVtZW50YWxfcmVncmVzc2lvbl9ieV9pZCA8LSBmdW5jdGlvbihpZF9pLCBpZF9uYW1lLCBkYXRhLCBwcmVkaWN0b3IsIHJlc3BvbnNlLCBzYXZlX21vZGVscywgbW9kX291dHB1dF93ZCkgew0KICAjIEluaXRpYXRlIGFuIGVtcHR5IGxpc3QgdG8gc3RvcmUgbW9kZWxzDQogIG1vZGVscyA8LSBsaXN0KCkNCiAgDQogICMgSWYgaWRfaSBhbmQgaWRfbmFtZSBpcyBlbXB0eSBhZGQNCiAgDQogICMgRmlsdGVyIGRhdGEgZm9yIHRoZSBjdXJyZW50IElEDQogIGRmX2kgPC0gZGF0YSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKCEhcmxhbmc6OnN5bShpZF9uYW1lKSA9PSBpZF9pKQ0KICANCiAgIyBEeW5hbWljYWxseSBjcmVhdGUgZm9ybXVsYXMNCiAgZm9ybXVsYV9sbV8wIDwtIHJlZm9ybXVsYXRlKCIxIiwgcmVzcG9uc2UpDQogIGZvcm11bGFfbG1fMSA8LSByZWZvcm11bGF0ZShwcmVkaWN0b3IsIHJlc3BvbnNlKQ0KICBmb3JtdWxhX2xtXzIgPC0gcmVmb3JtdWxhdGUoc3ByaW50ZigicG9seSglcywgMikiLCBwcmVkaWN0b3IpLCByZXNwb25zZSkNCiAgZm9ybXVsYV9sbV8zIDwtIHJlZm9ybXVsYXRlKHNwcmludGYoInBvbHkoJXMsIDMpIiwgcHJlZGljdG9yKSwgcmVzcG9uc2UpDQogIA0KICAjIEZpdCBhbmQgc3RvcmUgbW9kZWxzIGluIHRoZSBsaXN0DQogIG1vZGVsc1tbcGFzdGUwKGlkX2ksICJfbG1fMCIpXV0gPC0gYnJtKA0KICAgIGJmKGZvcm11bGFfbG1fMCwgZmFtaWx5ID0gZ2F1c3NpYW4oKSksIA0KICAgIGRhdGEgPSBkZl9pLCBjb3JlcyA9IDQsIHNlZWQgPSAxNDMwMTksIHNhdmVfcGFycyA9IHNhdmVfcGFycyhhbGwgPSBzYXZlX21vZGVscyksDQogICAgc2FtcGxlX3ByaW9yID0gRkFMU0UsIHNpbGVudCA9IFRSVUUsIGZpbGUgPSBwYXN0ZTAobW9kX291dHB1dF93ZCwgIi8iLCBpZF9pLCAiX2xtXzAiKQ0KICApDQogIA0KICBtb2RlbHNbW3Bhc3RlMChpZF9pLCAiX2xtXzEiKV1dIDwtIGJybSgNCiAgICBiZihmb3JtdWxhX2xtXzEsIGZhbWlseSA9IGdhdXNzaWFuKCkpLCANCiAgICBkYXRhID0gZGZfaSwgY29yZXMgPSA0LCBzZWVkID0gMTQzMDE5LCBzYXZlX3BhcnMgPSBzYXZlX3BhcnMoYWxsID0gc2F2ZV9tb2RlbHMpLA0KICAgIHNhbXBsZV9wcmlvciA9IEZBTFNFLCBzaWxlbnQgPSBUUlVFLCBmaWxlID0gcGFzdGUwKG1vZF9vdXRwdXRfd2QsICIvIiwgaWRfaSwgIl9sbV8xIikNCiAgKQ0KICANCiAgbW9kZWxzW1twYXN0ZTAoaWRfaSwgIl9sbV8yIildXSA8LSBicm0oDQogICAgYmYoZm9ybXVsYV9sbV8yLCBmYW1pbHkgPSBnYXVzc2lhbigpKSwgDQogICAgZGF0YSA9IGRmX2ksIGNvcmVzID0gNCwgc2VlZCA9IDE0MzAxOSwgc2F2ZV9wYXJzID0gc2F2ZV9wYXJzKGFsbCA9IHNhdmVfbW9kZWxzKSwNCiAgICBzYW1wbGVfcHJpb3IgPSBGQUxTRSwgc2lsZW50ID0gVFJVRSwgZmlsZSA9IHBhc3RlMChtb2Rfb3V0cHV0X3dkLCAiLyIsIGlkX2ksICJfbG1fMiIpDQogICkNCiAgDQogIG1vZGVsc1tbcGFzdGUwKGlkX2ksICJfbG1fMyIpXV0gPC0gYnJtKA0KICAgIGJmKGZvcm11bGFfbG1fMywgZmFtaWx5ID0gZ2F1c3NpYW4oKSksIA0KICAgIGRhdGEgPSBkZl9pLCBjb3JlcyA9IDQsIHNlZWQgPSAxNDMwMTksIHNhdmVfcGFycyA9IHNhdmVfcGFycyhhbGwgPSBzYXZlX21vZGVscyksDQogICAgc2FtcGxlX3ByaW9yID0gRkFMU0UsIHNpbGVudCA9IFRSVUUsIGZpbGUgPSBwYXN0ZTAobW9kX291dHB1dF93ZCwgIi8iLCBpZF9pLCAiX2xtXzMiKQ0KICApDQogIA0KICAjIFJldHVybiB0aGUgbGlzdCBvZiBtb2RlbHMgZm9yIHRoZSBjdXJyZW50IElEDQogIHJldHVybihtb2RlbHMpDQp9DQoNCmBgYA0KDQoqKmxvYWRfcmRzKio6IEEgY3VzdG9tIGZ1bmN0aW9uIHRvIGxvYWQgYWxsIHJkcyBtb2RlbHMgaW4gYSBkaXJlY3RvcnkgYW5kIHN0b3JlIGluIGEgbGlzdA0KDQpgYGB7cn0NCmxvYWRfcmRzIDwtIGZ1bmN0aW9uKG1vZGVsX2R3KSB7DQogICMgTGlzdCBhbGwgLnJkcyBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5DQogIG1vZGVsX2ZpbGVfbGlzdCA8LSBsaXN0LmZpbGVzKHBhdGggPSBtb2RlbF9kdywgcGF0dGVybiA9ICJcXC5yZHMkIiwgZnVsbC5uYW1lcyA9IFRSVUUpDQogIA0KICAjIEluaXRpYWxpc2UgYW4gZW1wdHkgbGlzdCB0byBzdG9yZSBtb2RlbHMNCiAgbW9kZWxfc3RvcmVfbGlzdCA8LSBsaXN0KCkNCiAgDQogICMgSXRlcmF0ZSB0aHJvdWdoIGVhY2ggZmlsZSBhbmQgbG9hZCB0aGUgUkRTDQogIGZvciAobW9kX2kgaW4gbW9kZWxfZmlsZV9saXN0KSB7DQogICAgbW9kIDwtIHJlYWRSRFMoZmlsZSA9IG1vZF9pKSAgIyBSZWFkIHRoZSBSRFMgZmlsZQ0KICAgIG1vZGVsX25hbWUgPC0gdG9vbHM6OmZpbGVfcGF0aF9zYW5zX2V4dChiYXNlbmFtZShtb2RfaSkpICAjIEV4dHJhY3QgdGhlIGZpbGUgbmFtZSB3aXRob3V0IGV4dGVuc2lvbg0KICAgIG1vZGVsX3N0b3JlX2xpc3RbW21vZGVsX25hbWVdXSA8LSBtb2QgICMgU3RvcmUgaXQgaW4gdGhlIGxpc3QNCiAgfQ0KICANCiAgIyBSZXR1cm4gdGhlIGxpc3Qgb2YgbW9kZWxzDQogIHJldHVybihtb2RlbF9zdG9yZV9saXN0KQ0KfQ0KYGBgDQoNCg0KKippbmNyZW1lbnRhbF9yZWdyZXNzaW9uX2JheWVzX2ZpdHMqKjogQSBjdXN0b20gZnVuY3Rpb24gZm9yIHB1bGxpbmcgbW9kZWwgZml0cywgbG9vIGFuZCByMg0KDQpgYGB7cn0NCiMgRGVmaW5lIEZ1bmN0aW9uIHRvIFByb2Nlc3MgdGhlIGRhdGEgZm9yIGVhY2ggSUQNCmluY3JlbWVudGFsX3JlZ3Jlc3Npb25fYmF5ZXNfZml0cyA8LSBmdW5jdGlvbihtb2RlbHMpIHsNCiAgDQogIGxvb19yZXN1bHRzX2xpc3QgPC0gbGlzdCgpDQogIA0KICAjIEl0ZXJhdGUgb3ZlciB0aGUgbmFtZXMgb2YgdGhlIG1vZGVscw0KICBmb3IgKG1vZF9uYW1lIGluIG5hbWVzKG1vZGVscykpIHsNCiAgICAjIEV4dHJhY3QgdGhlIG1vZGVsDQogICAgbW9kX2kgPC0gbW9kZWxzW1ttb2RfbmFtZV1dDQogICAgDQogICAgIyBDb21wdXRlIExPTyByZXN1bHRzDQogICAgbW9kX2xvb19yZXN1bHRzX2kgPC0gbG9vOjpsb28obW9kX2kpDQogICAgDQogICAgIyBFeHRyYWN0IHJlbGV2YW50IExPTyBtZXRyaWNzDQogICAgZWxwZF9sb29faSA8LSBtb2RfbG9vX3Jlc3VsdHNfaSRlbHBkX2xvbw0KICAgIHBfbG9vX2kgPC0gbW9kX2xvb19yZXN1bHRzX2kkcF9sb28NCiAgICBsb29pY19pIDwtIG1vZF9sb29fcmVzdWx0c19pJGxvb2ljDQogICAgDQogICAgIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggbWV0cmljcw0KICAgIGRmX2kgPC0gZGF0YS5mcmFtZSgNCiAgICAgIGVscGRfbG9vID0gZWxwZF9sb29faSwNCiAgICAgIHBfbG9vID0gcF9sb29faSwNCiAgICAgIGxvb2ljID0gbG9vaWNfaSwNCiAgICAgIG1vZGVsID0gbW9kX25hbWUNCiAgICApDQogICAgDQogICAgZXN0X2kgPC0gdGlkeShtb2RfaSwgZWZmZWN0cyA9ICJmaXhlZCIsIGNvbmYuaW50ID0gVFJVRSkgJT4lIA0KICAgICAgZHBseXI6OnNlbGVjdCh0ZXJtLCBlc3RpbWF0ZSwgY29uZi5sb3csIGNvbmYuaGlnaCkgJT4lIA0KICAgICAgdGlkeXI6OnBpdm90X3dpZGVyKA0KICAgICAgICBuYW1lc19mcm9tID0gdGVybSwgICAgICAgICAgICAgICAgIyBVc2UgYHRlcm1gIGFzIGNvbHVtbiBuYW1lcw0KICAgICAgICB2YWx1ZXNfZnJvbSA9IGMoZXN0aW1hdGUsIGNvbmYubG93LCBjb25mLmhpZ2gpLCAgIyBWYWx1ZXMgdG8gcGl2b3QNCiAgICAgICAgbmFtZXNfc2VwID0gIl8iICAgICAgICAgICAgICAgICAgICMgQWRkIGEgc2VwYXJhdG9yIHRvIGNvbHVtbiBuYW1lcw0KICAgICAgKQ0KICAgIA0KICAgIGRmX2kgPC0gY2JpbmQoZGZfaSwgZXN0X2kpDQogICAgDQogICAgIyBTdG9yZSB0aGUgZGF0YSBmcmFtZSBpbiB0aGUgbGlzdA0KICAgIGxvb19yZXN1bHRzX2xpc3RbW21vZF9uYW1lXV0gPC0gZGZfaQ0KICB9DQogIA0KICAjIENvbWJpbmQgDQogIGxvb19yZXN1bHRzX2NvbWJpbmVkIDwtIGJpbmRfcm93cyhsb29fcmVzdWx0c19saXN0KQ0KICANCiAgIyBHZXQgUjIgDQogIHIyX3Jlc3VsdHMgPC0gbWFwX2Rmcihtb2RlbHMsIH4gYXMuZGF0YS5mcmFtZShiYXllc19SMigueCkpLCAuaWQgPSAibW9kZWwiKSAlPiUNCiAgICB0aWJibGU6OnJlbW92ZV9yb3duYW1lcygpDQogIA0KICAjIENvbWJpbmQgUjIgYW5kIGxvbyByZXN1bHRzIA0KICBtb2RlbF9maXRfZGYgPC0gZHBseXI6OmZ1bGxfam9pbihsb29fcmVzdWx0c19jb21iaW5lZCwgcjJfcmVzdWx0cywgYnkgPSAibW9kZWwiKSAlPiUgDQogICAgZHBseXI6OnNlbGVjdChtb2RlbCwgZXZlcnl0aGluZygpKSAlPiUgDQogICAgZHBseXI6OnJlbmFtZShyMiA9IEVzdGltYXRlLA0KICAgICAgICAgICAgICAgICAgcjJfZXJyb3IgPSBFc3QuRXJyb3IsDQogICAgICAgICAgICAgICAgICByMl9xMi41ID0gUTIuNSwNCiAgICAgICAgICAgICAgICAgIHIyX3E5Ny41ID0gUTk3LjUpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKGlkID0gc3ViKCJfKGxtX1xcZCspJCIsICIiLCBtb2RlbCksDQogICAgICAgICAgICAgICAgICBtb2RlbF90eXBlID0gc3ViKCJeLipfKGxtX1xcZCspJCIsICJcXDEiLCBtb2RlbCkpDQogIA0KICByZXR1cm4obW9kZWxfZml0X2RmKQ0KfQ0KYGBgDQoNCioqYmF5ZXNfbW9kX3ByZWRpY3Rpb25zKio6IFRoaXMgZnVuY3Rpb24gZXh0cmFjdHMgdGhlIHByZWRpY3RlZCB2YWx1ZXMgZnJvbSBhIGxpc3Qgb2YgbW9kZWxzIGFuZCBjb21iaW5kcyBpdCB3aXRoIHRoZSBvcmduaWFsIGRhdGEgZmlsZSB1c2VkIGZvciB0aGUgbW9kZWwNCg0KYGBge3J9DQpiYXllc19tb2RfcHJlZGljdGlvbnMgPC0gZnVuY3Rpb24obW9kZWxzLCBvcmlnaW5hbF9kYXRhKSB7DQogIA0KICBwcmVkaWN0aW9uX2xpc3QgPC0gbGlzdCgpDQogIA0KICBmb3IgKG1vZF9uYW1lIGluIG5hbWVzKG1vZGVscykpIHsNCiAgICAjIEV4dHJhY3QgbW9kDQogICAgbW9kX2kgPC0gbW9kZWxzW1ttb2RfbmFtZV1dDQogICAgDQogICAgIyBNYWtlIG1vZGUgcHJlZGljdGlvbnMNCiAgICBtb2RlbF9wcmVkaWN0aW9uc19pIDwtIGFzLmRhdGEuZnJhbWUoZml0dGVkKG1vZF9pLCBzdW1tYXJ5ID0gVFJVRSkpICU+JSANCiAgICAgIGRwbHlyOjptdXRhdGUobW9kZWwgPSBtb2RfbmFtZSwNCiAgICAgICAgICAgICAgICAgICAgaWQgPSBzdWIoIl8obG1fXFxkKykkIiwgIiIsIG1vZF9uYW1lKSwNCiAgICAgICAgICAgICAgICAgICAgbW9kZWxfdHlwZSA9IHN1YigiXi4qXyhsbV9cXGQrKSQiLCAiXFwxIiwgbW9kX25hbWUpKSAlPiUgDQogICAgICBkcGx5cjo6cmVuYW1lKHByZWRfbG93ZXIgPSBRMi41LCBwcmVkX3VwcGVyID0gUTk3LjUsIHByZWRpY3RlZCA9IEVzdGltYXRlLCBwcmVkX2Vycm9yID0gRXN0LkVycm9yKSAlPiUgDQogICAgICBkcGx5cjo6c2VsZWN0KG1vZGVsLCBldmVyeXRoaW5nKCkpDQogICAgDQogICAgaWRfaSA8LSBtb2RlbF9wcmVkaWN0aW9uc19pJGlkWzFdDQogICAgDQogICAgb3JpZ2luYWxfZGF0YV9pIDwtIG9yaWdpbmFsX2RhdGEgJT4lIA0KICAgICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKQ0KICAgIA0KICAgIG1vZGVsX3ByZWRpY3Rpb25zX29yaWdpbmFsX2kgPC0gY2JpbmQobW9kZWxfcHJlZGljdGlvbnNfaSwgb3JpZ2luYWxfZGF0YV9pKQ0KICAgIA0KICAgIHByZWRpY3Rpb25fbGlzdFtbbW9kX25hbWVdXSA8LSBtb2RlbF9wcmVkaWN0aW9uc19vcmlnaW5hbF9pDQogIH0NCiAgcHJlZGljdGlvbnNfZGYgPC0gYmluZF9yb3dzKHByZWRpY3Rpb25fbGlzdCkgDQogIHJldHVybihwcmVkaWN0aW9uc19kZikNCn0NCmBgYA0KDQoNCioqY2FsY1NNUioqOiBhdXRob3JlZCBieSBDaGFib3QgRC4gdXNlZCB0byBlc3RpbWF0ZSBTTVIgd2l0aCBzZXZlcmFsIGRpZmZlcmVudCBtZXRob2RzIENsYWlyZWF1eCBhbmQgQ2hhYm90ICgyMDE2KSBET0k6IGRvaToxMC4xMTExL2pmYi4xMjgzMw0KDQpgYGB7cn0NCmNhbGNTTVIgPSBmdW5jdGlvbihZLCBxPWMoMC4xLDAuMTUsMC4yLDAuMjUsMC4zKSwgRz0xOjQpew0KCXUgPSBzb3J0KFkpDQoJdGhlLk1jbHVzdCA8LSBNY2x1c3QoWSwgIEc9RykNCgljbCA8LSB0aGUuTWNsdXN0JGNsYXNzaWZpY2F0aW9uDQoJIyBzb21ldGltZXMsIHRoZSBjbGFzcyBjb250YWluaW5nIFNNUiBpcyBub3QgY2FsbGVkIDENCgkjIHRoZSBmb2xsb3dpbmcgcHJlc3VtZXMgdGhhdCB3aGVuIGNsYXNzIDEgY29udGFpbnMgPiAxMCUgb2YgY2FzZXMsIA0KCSMgaXQgY29udGFpbnMgU01SLCBvdGhlcndpc2Ugd2UgdGFrZSBjbGFzcyAyDQoJY2wyIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoY2wpKQ0KCWNsMiRjbCA8LSBhcy5udW1lcmljKGxldmVscyhjbDIkY2wpKQ0KCXZhbGlkIDwtIGNsMiRGcmVxPj0wLjEqbGVuZ3RoKHRpbWUpICANCgl0aGUuY2wgPC0gbWluKGNsMiRjbFt2YWxpZF0pDQoJbGVmdC5kaXN0ciA8LSBZW3RoZS5NY2x1c3QkY2xhc3NpZmljYXRpb249PXRoZS5jbF0NCgltbG5kID0gdGhlLk1jbHVzdCRwYXJhbWV0ZXJzJG1lYW5bdGhlLmNsXQ0KCUNWbWxuZCA9IHNkKGxlZnQuZGlzdHIpL21sbmQgKiAxMDANCglxdWFudD1xdWFudGlsZShZLCBxKQ0KCWxvdzEwPW1lYW4odVsxOjEwXSkNCglsb3cxMHBjID0gbWVhbih1WzY6KDUgKyByb3VuZCgwLjEqKGxlbmd0aCh1KS01KSkpXSkNCgkjIHJlbW92ZSA1IG91dGxpZXJzLCBrZWVwIGxvd2VzdCAxMCUgb2YgdGhlIHJlc3QsIGF2ZXJhZ2UNCgkjIEhlcnJtYW5uICYgRW5kZXJzIDIwMDANCglyZXR1cm4obGlzdChtbG5kPW1sbmQsIHF1YW50PXF1YW50LCBsb3cxMD1sb3cxMCwgbG93MTBwYz1sb3cxMHBjLA0KCQkgICAgICBjbD1jbCwgQ1ZtbG5kPUNWbWxuZCkpDQp9DQpgYGANCg0KDQoqKmNhbGNPMmNyaXQqKjogYXV0aG9yZWQgYnkgQ2hhYm90IEQuIHVzZWQgdG8gZXN0aW1hdGUgTzJjcml0IChQY3JpcHQpLiBDbGFpcmVhdXggYW5kIENoYWJvdCAoMjAxNikgRE9JOiBkb2k6MTAuMTExMS9qZmIuMTI4MzMNCg0KKioqTm90ZTogTzIgaXMgYXNzdW1lZCB0byBiZSBpbiBwZXJjZW50YWdlIG9mIGRpc3NvbHZlZCBveHlnZW4gKERPKSB0byB3b3JrKioqDQoNCmBgYHtyfQ0KY2FsY08yY3JpdCA8LSBmdW5jdGlvbihEYXRhLCBTTVIsIGxvd2VzdE1PMj1OQSwgZ2FwTGltaXQgPSA0LA0KbWF4Lm5iLk1PMi5mb3IucmVnID0gMjApDQp7DQojIEFVVEhPUjogRGVuaXMgQ2hhYm90LCBJbnN0aXR1dCBNYXVyaWNlLUxhbW9udGFnbmUsIERGTywgQ2FuYWRhDQojIGZpcnN0IHZlcnNpb24gd3JpdHRlbiBpbiBKdW5lIDIwMDkNCiMgbGFzdCB1cGRhdGVkIGluIEphbnVhcnkgMjAxNQ0KbWV0aG9kID0gIkxTX3JlZyIgIyB3aWxsIGJlY29tZSAidGhyb3VnaF9vcmlnaW4iIGlmIGludGVyY2VwdCBpcyA+IDANCmlmKGlzLm5hKGxvd2VzdE1PMikpIGxvd2VzdE1PMiA9IHF1YW50aWxlKERhdGEkTU8yW0RhdGEkRE8gPj0gODBdLCBwPTAuMDUpDQojIFN0ZXAgMTogaWRlbnRpZnkgcG9pbnRzIHdoZXJlIE1PMiBpcyBwcm9wb3J0aW9uYWwgdG8gRE8NCmdlcVNNUiA9IERhdGEkTU8yID49IGxvd2VzdE1PMg0KcGl2b3RETyA9IG1pbihEYXRhJERPW2dlcVNNUl0pDQpsZXRoYWwgPSBEYXRhJERPIDwgcGl2b3RETw0KTl91bmRlcl9TTVIgPSBzdW0obGV0aGFsKSAjIHBvaW50cyBhdmFpbGFibGUgZm9yIHJlZ3Jlc3Npb24/DQpmaW5hbF9OX3VuZGVyX1NNUiA9IGxldGhhbCAjIHNvbWUgcG9pbnRzIG1heSBiZSByZW1vdmVkIGF0IFN0ZXAgNA0KbGFzdE1PMnJlZyA9IERhdGEkTU8yW0RhdGEkRE8gPT0gcGl2b3RET10gIyBsYXN0IE1PMiB3aGVuIHJlZ3VsYXRpbmcNCmlmKE5fdW5kZXJfU01SID4gMSkgdGhlTW9kID0gbG0oTU8yfkRPLCBkYXRhPURhdGFbbGV0aGFsLF0pDQojIFN0ZXAgMiwgYWRkIG9uZSBvciBtb3JlIHBvaW50IGF0IG9yIGFib3ZlIFNNUg0KIyAyQSwgd2hlbiB0aGVyZSBhcmUgZmV3ZXIgdGhhbiAzIHZhbGlkIHBvaW50cyB0byBjYWxjdWxhdGUgYSByZWdyZXNzaW9uDQppZihOX3VuZGVyX1NNUiA8IDMpew0KbWlzc2luZyA9IDMgLSBzdW0obGV0aGFsKQ0Kbm90LmxldGhhbCA9IERhdGEkRE9bZ2VxU01SXQ0KRE9saW1pdCA9IG1heChzb3J0KG5vdC5sZXRoYWwpWzE6bWlzc2luZ10pICMgaGlnaGVzdCBETyBhY2NlcHRhYmxlDQojIHRvIHJlYWNoIGEgTiBvZiAzDQphZGRlZFBvaW50cyA9IERhdGEkRE8gPD0gRE9saW1pdA0KbGV0aGFsID0gbGV0aGFsIHwgYWRkZWRQb2ludHMNCnRoZU1vZCA9IGxtKE1PMn5ETywgZGF0YT1EYXRhW2xldGhhbCxdKQ0KfQ0KIyAyQiwgYWRkIHBpdm90RE8gdG8gdGhlIGZpdCB3aGVuIFN0ZXAgMSB5aWVsZGVkIDMgb3IgbW9yZSB2YWx1ZXM/DQppZihOX3VuZGVyX1NNUiA+PSAzKXsNCmxldGhhbEIgPSBEYXRhJERPIDw9IHBpdm90RE8gIyBoYXMgb25lIG1vcmUgdmFsdWUgdGhhbiAibGV0aGFsIg0KcmVnQSA9IHRoZU1vZA0KcmVnQiA9IGxtKE1PMn5ETywgZGF0YT1EYXRhW2xldGhhbEIsXSkNCmxhcmdlX3Nsb3BlX2Ryb3AgPSAoY29lZihyZWdBKVsyXS9jb2VmKHJlZ0IpWzJdKSA+IDEuMSAjIGFyYml0cmFyeQ0KbGFyZ2VfRE9fZ2FwID0gKG1heChEYXRhJERPW2xldGhhbEJdKSAtIG1heChEYXRhJERPW2xldGhhbF0pKSA+IGdhcExpbWl0DQp0b29TbWFsbE1PMiA9IGxhc3RNTzJyZWcgPCBTTVINCmlmKCFsYXJnZV9zbG9wZV9kcm9wICYgIWxhcmdlX0RPX2dhcCAmICF0b29TbWFsbE1PMikgew0KbGV0aGFsID0gbGV0aGFsQg0KdGhlTW9kID0gcmVnQg0KfSAjIG90aGVyd2lzZSB3ZSBkbyBub3QgYWNjZXB0IHRoZSBhZGRpdGlvbmFsIHBvaW50DQp9DQojIFN0ZXAgMw0KIyBpZiB0aGUgdXNlciB3YW50cyB0byBsaW1pdCB0aGUgbnVtYmVyIG9mIHBvaW50cyBpbiB0aGUgcmVncmVzc2lvbg0KaWYoIWlzLm5hKG1heC5uYi5NTzIuZm9yLnJlZykgJiBzdW0obGV0aGFsKT5tYXgubmIuTU8yLmZvci5yZWcpew0KUmFua3MgPSByYW5rKERhdGEkRE8pDQpsZXRoYWwgPSBSYW5rcyA8PSBtYXgubmIuTU8yLmZvci5yZWcNCnRoZU1vZCA9IGxtKE1PMn5ETywgZGF0YT1EYXRhW2xldGhhbCxdKQ0KZmluYWxfTl91bmRlcl9TTVIgPSBtYXgubmIuTU8yLmZvci5yZWcNCn0NCiMgU3RlcCA0DQpwcmVkTU8yID0gYXMubnVtZXJpYyhwcmVkaWN0KHRoZU1vZCwgZGF0YS5mcmFtZShETz1EYXRhJERPKSkpDQpEYXRhJGRlbHRhID0gKERhdGEkTU8yLXByZWRNTzIpL3ByZWRNTzIgKiAxMDAgIyByZXNpZHVhbHMgc2V0IHRvIHplcm8NCiMgd2hlbiBiZWxvdyBwaXZvdERPDQpEYXRhJGRlbHRhW0RhdGEkRE8gPCBwaXZvdERPIHwgbGV0aGFsXSA9IDANCnRvbCA9IDAgIyBhbnkgcG9zaXRpdmUgcmVzaWR1YWwgaXMgdW5hY2NlcHRhYmxlDQpIaWdoVmFsdWVzID0gRGF0YSRkZWx0YSA+IHRvbA0KUmFua3MgPSByYW5rKC0xKkRhdGEkZGVsdGEpDQpIaWdoTU8yID0gSGlnaFZhbHVlcyAmIFJhbmtzID09IG1pbihSYW5rcykgIyBrZWVwIGxhcmdlc3QgcmVzaWR1YWwNCmlmIChzdW0oSGlnaFZhbHVlcykgPiAwKSB7DQpuYmxldGhhbCA9IHN1bShsZXRoYWwpDQpEYXRhJFcgPSBOQQ0KRGF0YSRXW2xldGhhbF09MS9uYmxldGhhbA0KRGF0YSRXW0hpZ2hNTzJdID0gMQ0KdGhlTW9kID0gbG0oTU8yfkRPLCB3ZWlnaHQ9VywgZGF0YT1EYXRhW2xldGhhbCB8IEhpZ2hNTzIsXSkNCiMgVGhpcyBuZXcgcmVncmVzc2lvbiBpcyBhbHdheXMgYW4gaW1wcm92ZW1lbnQsIGJ1dCB0aGVyZSBjYW4gc3RpbGwNCiMgYmUgcG9pbnRzIGFib3ZlIHRoZSBsaW5lLCBzbyB3ZSByZXBlYXQNCnByZWRNTzJfMiA9IGFzLm51bWVyaWMocHJlZGljdCh0aGVNb2QsIGRhdGEuZnJhbWUoRE89RGF0YSRETykpKQ0KRGF0YSRkZWx0YTIgPSAoRGF0YSRNTzItcHJlZE1PMl8yKS9wcmVkTU8yXzIgKiAxMDANCkRhdGEkZGVsdGEyW0RhdGEkRE8gPCBwaXZvdERPXSA9IDANCnRvbCA9IERhdGEkZGVsdGEyW0hpZ2hNTzJdDQpIaWdoVmFsdWVzMiA9IERhdGEkZGVsdGEyID4gdG9sDQppZihzdW0oSGlnaFZhbHVlczIpPjApew0KUmFua3MyID0gcmFuaygtMSpEYXRhJGRlbHRhMikNCkhpZ2hNTzJfMiA9IEhpZ2hWYWx1ZXMyICYgUmFua3MyID09IDEgIyBrZWVwIHRoZSBsYXJnZXN0IHJlc2lkdWFsDQpuYmxldGhhbCA9IHN1bShsZXRoYWwpDQpEYXRhJFcgPSBOQQ0KRGF0YSRXW2xldGhhbF09MS9uYmxldGhhbA0KRGF0YSRXW0hpZ2hNTzJfMl0gPSAxDQp0aGVNb2QyID0gbG0oTU8yfkRPLCB3ZWlnaHQ9VywgZGF0YT1EYXRhW2xldGhhbCB8IEhpZ2hNTzJfMixdKQ0KIyBpcyBuZXcgc2xvcGUgc3RlZXBlciB0aGFuIHRoZSBvbGQgb25lPw0KaWYodGhlTW9kMiRjb2VmWzJdID4gdGhlTW9kJGNvZWZbMl0pIHsNCnRoZU1vZCA9IHRoZU1vZDINCkhpZ2hNTzIgPSBIaWdoTU8yXzINCn0NCn0gIyBlbmQgc2Vjb25kIHNlYXJjaCBmb3IgaGlnaCB2YWx1ZQ0KfSAjIGVuZCBmaXJzdCBzZWFyY2ggZm9yIGhpZ2ggdmFsdWUNCkNvZWYgPSBjb2VmZmljaWVudHModGhlTW9kKQ0KI1N0ZXAgNSwgY2hlY2sgZm9yIHBvc2l0aXZlIGludGVyY2VwdA0KQWJvdmVPcmlnaW4gPSBDb2VmWzFdID4gMA0KIyBpZiBpdCBpcywgd2UgdXNlIGEgcmVncmVzc2lvbiB0aGF0IGdvZXMgdGhyb3VnaCB0aGUgb3JpZ2luDQppZiAoQWJvdmVPcmlnaW4pew0KdGhlTW9kID0gbG0oTU8yfkRPIC0xLCBkYXRhPURhdGFbbGV0aGFsLF0pDQpDb2VmID0gYygwLCBjb2VmZmljaWVudHModGhlTW9kKSkgIyBuZWVkIHRvIGFkZCB0aGUgaW50ZXJjZXB0ICgwKQ0KIyBtYW51YWxseSB0byBoYXZlIGEgcGFpciBvZiBjb2VmZmljaWVudHMNCm1ldGhvZCA9ICJ0aHJvdWdoX29yaWdpbiINCkhpZ2hNTzIgPSByZXAoRkFMU0UsIG5yb3coRGF0YSkpICMgZGlkIG5vdCB1c2UgdGhlIGFkZGl0aW9uYWwgdmFsdWUNCiMgZnJvbSBTdGVwIDQNCn0NCnBvMmNyaXQgPSBhcy5udW1lcmljKHJvdW5kKChTTVIgLSBDb2VmWzFdKSAvIENvZWZbMl0sIDEpKQ0Kc3VtX21vZCA9IHN1bW1hcnkodGhlTW9kKQ0KYW5vdl9tb2QgPSBhbm92YSh0aGVNb2QpDQpPMkNSSVQgPSBsaXN0KG8yY3JpdD1wbzJjcml0LCBTTVI9U01SLCBOYl9NTzJfY29uZm9ybWluZyA9IE5fdW5kZXJfU01SLA0KTmJfTU8yX2NvbmZfdXNlZCA9IGZpbmFsX05fdW5kZXJfU01SLA0KSGlnaF9NTzJfcmVxdWlyZWQgPSBzdW0oSGlnaE1PMikgPT0gMSwgb3JpZ0RhdGE9RGF0YSwNCk1ldGhvZD1tZXRob2QsIG1vZD10aGVNb2QsIHIyID0gc3VtX21vZCRyLnNxdWFyZWQsDQpQID0gYW5vdl9tb2QkIlByKD5GKSIsIGxldGhhbFBvaW50cyA9IHdoaWNoKGxldGhhbCksDQpBZGRlZFBvaW50cyA9IHdoaWNoKEhpZ2hNTzIpKQ0KfSAjIGVuZCBmdW5jdGlvbg0KYGBgDQoNCioqcGxvdE8yY3JpdCoqOiB1c2VkIHRvIHBsb3QgdGhlIG1vZGVzIHVzZWQgZm9yIHRoZSBjYWxjTzJjcml0IGZ1bmN0aW9uLiBDbGFpcmVhdXggYW5kIENoYWJvdCAoMjAxNikgRE9JOiBkb2k6MTAuMTExMS9qZmIuMTI4MzMNCg0KYGBge3J9DQpwbG90TzJjcml0IDwtIGZ1bmN0aW9uKG8yY3JpdG9iaiwgcGxvdElEPSIiLA0KWGxhYj0iRGlzc29sdmVkIG94eWdlbiAoJSBzYXQuKSIsIFlsYWI9ImRvdGl0YWx1bW9sIiwNCnNtci5jZXg9MC45LCBvMmNyaXQuY2V4PTAuOSwgcGxvdElELmNleD0xLjIsDQpUcmFuc3BhcmVuY3k9VCwuLi4pDQp7DQojIEFVVEhPUjogRGVuaXMgQ2hhYm90LCBJbnN0aXR1dCBNYXVyaWNlLUxhbW9udGFnbmUsIERGTywgQ2FuYWRhDQojIGZpcnN0IHZlcnNpb24gd3JpdHRlbiBpbiBKdW5lIDIwMDkNCiMgbGFzdCB1cGRhdGVkIDIwMTUtMDItMDkNCiMgZm9yIFIgcGxvdHRpbmcgZGV2aWNlcyB0aGF0IGRvIG5vdCBzdXBwb3J0IHRyYW5zcGFyZW5jeQ0KIyAoZS5nLiwgcG9zdHNjcmlwdCksIHNldCBUcmFuc3BhcmVuY3kgdG8gRkFMU0UNCnNtciA9IG8yY3JpdG9iaiRTTVINCmlmKFlsYWIgJWluJSBjKCJkb3RpdGFsdW1vbCIsICJpdGFsdW1vbCIsICJkb3R1bW9sIiwgInVtb2wiLA0KImRvdGl0YWxtZyIsICJpdGFsbWciLCAiZG90bWciLCAibWciKSkgew0Kc3dpdGNoKFlsYWIsDQpkb3RpdGFsdW1vbCA9IHsNCm1vMi5sYWIgPSBleHByZXNzaW9uKHBhc3RlKGl0YWxpYyhkb3QoTSkpW09bMl1dLCAiICgiLG11LCJtb2wgIiwgT1syXSwNCiIgIiwgbWluXi0xLCAiICIsIGtnXi0xLCAiKSIpKQ0KfSwNCml0YWx1bW9sID0gew0KbW8yLmxhYiA9IGV4cHJlc3Npb24ocGFzdGUoaXRhbGljKE0pW09bMl1dLCAiICgiLG11LCJtb2wgIiwgT1syXSwgIiAiLA0KbWluXi0xLCAiICIsIGtnXi0xLCAiKSIpKQ0KfSwNCmRvdHVtb2wgPSB7DQptbzIubGFiID0gZXhwcmVzc2lvbihwYXN0ZShkb3QoTSlbT1syXV0sICIgKCIsbXUsIm1vbCAiLCBPWzJdLCAiICIsDQptaW5eLTEsICIgIiwga2deLTEsICIpIikpDQp9LA0KdW1vbCA9IHsNCm1vMi5sYWIgPSBleHByZXNzaW9uKHBhc3RlKE1bT1syXV0sICIgKCIsbXUsIm1vbCAiLCBPWzJdLCAiICIsIG1pbl4tMSwNCiIgIiwga2deLTEsICIpIikpDQp9LA0KZG90aXRhbG1nID0gew0KbW8yLmxhYiA9IGV4cHJlc3Npb24ocGFzdGUoaXRhbGljKGRvdChNKSlbT1syXV0sICIgKG1nICIsIE9bMl0sICIgIiwNCmheLTEsICIgIiwga2deLTEsICIpIikpDQp9LA0KaXRhbG1nID0gew0KbW8yLmxhYiA9IGV4cHJlc3Npb24ocGFzdGUoaXRhbGljKE0pW09bMl1dLCAiIChtZyAiLCBPWzJdLCAiICIsDQpoXi0xLCAiICIsIGtnXi0xLCAiKSIpKQ0KfSwNCmRvdG1nID0gew0KbW8yLmxhYiA9IGV4cHJlc3Npb24ocGFzdGUoZG90KE0pW09bMl1dLCAiIChtZyAiLCBPWzJdLCAiICIsIGheLTEsICIgIiwNCmtnXi0xLCAiKSIpKQ0KfSwNCm1nID0gew0KbW8yLmxhYiA9IGV4cHJlc3Npb24ocGFzdGUoTVtPWzJdXSwgIiAobWcgIiwgT1syXSwgIiAiLCBoXi0xLCAiICIsDQprZ14tMSwgIikiKSkNCn0NCikNCn0gZWxzZSBtbzIubGFiPVlsYWINCmlmKFRyYW5zcGFyZW5jeSkge0NvbD1jKHJnYigwLDAsMCwwLjcpLCAicmVkIiwgIm9yYW5nZSIpDQp9IGVsc2Uge0NvbD1jKGdyZXkoMC4zKSwgInJlZCIsICJvcmFuZ2UiKX0NCkRhdGE9bzJjcml0b2JqJG9yaWdEYXRhDQpsb3dlc3RNTzIgPSBxdWFudGlsZShEYXRhJE1PMltEYXRhJERPID49IDgwXSwgcD0wLjA1KSAjIEkgYWRkZWQgdGhpcw0KRGF0YSRDb2xvciA9IENvbFsxXQ0KRGF0YSRDb2xvcltvMmNyaXRvYmokbGV0aGFsUG9pbnRzXSA9IENvbFsyXQ0KRGF0YSRDb2xvcltvMmNyaXRvYmokQWRkZWRQb2ludHNdID0gQ29sWzNdDQojIG9yZGluYXJ5IExTIHJlZ3Jlc3Npb24gd2l0aG91dCBhZGRlZCBwb2ludHM6IGJsdWUgbGluZSwgcmVkIHN5bWJvbHMNCiMgb3JkaW5hcnkgTFMgcmVncmVzc2lvbiB3aXRoIGFkZGVkIHBvaW50czogYmx1ZSBsaW5lLCByZWQgJiBvcmFuZ2Ugc3ltYm9scw0KIyByZWdyZXNzaW9uIHRocm91Z2ggb3JpZ2luOiBncmVlbiBkb3R0ZWQgbGluZSwgcmVkIHN5bWJvbHMNCmxpbmUuY29sb3IgPSBpZmVsc2UobzJjcml0b2JqJE1ldGhvZD09IkxTX3JlZyIsICJibHVlIiwgImRhcmtncmVlbiIpDQpsaW5lLnR5cGUgPSBpZmVsc2UobzJjcml0b2JqJE1ldGhvZD09IkxTX3JlZyIsIDEsIDMpDQpsaW1YID0gYygwLCBtYXgoRGF0YSRETykpDQpsaW1ZID0gYygwLCBtYXgoRGF0YSRNTzIpKQ0KcGxvdChNTzJ+RE8sIGRhdGE9RGF0YSwgeGxpbT1saW1YLCB5bGltPWxpbVksIGNvbD1EYXRhJENvbG9yLCB4bGFiPVhsYWIsDQp5bGFiPW1vMi5sYWIsIC4uLikNCmNvb3JkIDwtIHBhcigidXNyIikNCmlmKHBsb3RJRCAhPSAiIil7DQp0ZXh0KDAsIGNvb3JkWzRdLCBwbG90SUQsIGNleD1wbG90SUQuY2V4LCBhZGo9YygwLDEuMikpDQp9DQphYmxpbmUoaD1sb3dlc3RNTzIsIGNvbD0icGluayIpICMgSSBhZGRlZCB0aGlzDQphYmxpbmUoaD1zbXIsIGNvbD0ib3JhbmdlIikNCnRleHQoY29vcmRbMV0sIHNtciwgIlNNUiIsIGFkaj1jKC0wLjEsMS4zKSwgY2V4PXNtci5jZXgpDQp0ZXh0KGNvb3JkWzFdLCBzbXIsIHJvdW5kKHNtciwxKSwgYWRqPWMoLTAuMSwtMC4zKSwgY2V4PXNtci5jZXgpDQppZighaXMubmEobzJjcml0b2JqJG8yY3JpdCkpIHsNCmFibGluZShvMmNyaXRvYmokbW9kLCBjb2w9bGluZS5jb2xvciwgbHR5PWxpbmUudHlwZSkNCnNlZ21lbnRzKG8yY3JpdG9iaiRvMmNyaXQsIHNtciwgbzJjcml0b2JqJG8yY3JpdCwgY29vcmRbM10sDQpjb2w9bGluZS5jb2xvciwgbHdkPTEpDQp0ZXh0KHg9bzJjcml0b2JqJG8yY3JpdCwgeT0wLCBvMmNyaXRvYmokbzJjcml0LCBjb2w9bGluZS5jb2xvciwNCmNleD1vMmNyaXQuY2V4LCBhZGo9YygtMC4xLDAuNSkpDQp9DQp9ICMgZW5kIG9mIGZ1bmN0aW9uDQpgYGANCg0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIFdvcmtpbmcgZGlyZWN0b3JpZXMgDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMjIElucHV0DQoNCioqbWV0YV9maWxlc193ZCoqOiBEaXJlY3RvcnkgZm9yIHRoZSBtZXRhZGF0YQ0KDQpgYGB7cn0NCndkIDwtIGdldHdkKCkNCm1ldGFfZmlsZXNfd2QgPC0gcGFzdGUwKHdkLCAiLi9tZXRhLWRhdGEiKSAjIGNyZWF0ZXMgYSB2YXJpYWJsZSB3aXRoIHRoZSBuYW1lIG9mIHRoZSB3ZCB3ZSB3YW50IHRvIHVzZQ0KYGBgDQoNCioqbGFiY2hhcnRfd2QqKjogRGlyZWN0b3J5IGZvciBMYWJjaGFydCBlc3RpbWF0ZWQgc2xvcGVzDQoNCmBgYHtyfQ0KbGFiY2hhcnRfd2QgPC0gcGFzdGUwKHdkLCAiLi9sYWItY2hhcnQtc2xvcGVzIikNCmBgYA0KDQoqKm1vZF9kYXRhX3dkKio6IERpcmVjdG9yeSBmb3IgbW9kZWwgb3V0cHV0IGRhdGEgZXN0aW1hdGVkIHNsb3Blcw0KDQpgYGB7cn0NCm1vZF9kYXRhX3dkIDwtIHBhc3RlMCh3ZCwgIi4vbW9kLWRhdGEiKQ0KYGBgDQoNCiMjIE91dHB1dA0KDQoqKm91dHB1dF9maWdfd2QqKjogdGhpcyBpcyB3aGVyZSB3ZSB3aWxsIHB1dCB0aGUgZmlndXJlcw0KDQpgYGB7cn0NCm91dHB1dF9maWdfd2QgPC0gcGFzdGUwKHdkLCAiLi9vdXRwdXQtZmlnIikNCmlmZWxzZSghZGlyLmV4aXN0cygib3V0cHV0LWZpZyIpLCBkaXIuY3JlYXRlKCJvdXRwdXQtZmlnIiksICJGb2xkZXIgYWxyZWFkeSBleGlzdHMiKQ0KYGBgDQoNCioqb3V0cHV0X21vZHNfd2QqKjogdGhpcyBpcyB3aGVyZSB3ZSB3aWxsIHB1dCB0aGUgZmlndXJlcw0KDQpgYGB7cn0NCm91dHB1dF9tb2RzX3dkIDwtIHBhc3RlMCh3ZCwgIi4vb3V0cHV0LW1vZCIpDQppZmVsc2UoIWRpci5leGlzdHMoIm91dHB1dC1tb2QiKSwgZGlyLmNyZWF0ZSgib3V0cHV0LW1vZCIpLCAiRm9sZGVyIGFscmVhZHkgZXhpc3RzIikNCmBgYA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIElucHV0IGZpbGVzDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMjIFNsb3BlcyAoTU8yKQ0KDQoqKnNsb3BlX2RmKio6IFdlIGhhdmUgaW1wb3J0ZWQgdGhlIHNsb3BlcyBleHRyYWN0ZWQgaW4gTGFiQ2hhcnQgZHVyaW5nIGVhY2ggcGhhc2Ugb2YgdGhlIGV4cGVyaW1lbnQNCg0KYGBge3J9DQogc2V0d2QobGFiY2hhcnRfd2QpDQojIA0KIyAjIEdldCB0aGUgbmFtZXMgb2YgYWxsIHNoZWV0cyBpbiB0aGUgRXhjZWwgZmlsZQ0Kc2hlZXRfbmFtZXMgPC0gZXhjZWxfc2hlZXRzKCJsYWJjaGFydC1hbGwtZGF0ZXNfdjIueGxzeCIpDQphbGxfdHJpYWxzX3NlbGVjdCA8LSBjKCJzdGFydF9kYXRlIiwgIm9yZGVyIiwgInBoYXNlIiwgImN5Y2xlIiwgImRhdGUiLCAidGltZSIpDQpzbG9wZV9saXN0IDwtIGxpc3QoKQ0KDQpmb3IgKHNoZWV0IGluIHNoZWV0X25hbWVzKSB7DQoNCiAgZGYgPC0gcmVhZF9leGNlbCgibGFiY2hhcnQtYWxsLWRhdGVzX3YyLnhsc3giLCBzaGVldCA9IHNoZWV0KSAlPiUgDQogIGRwbHlyOjpyZW5hbWVfd2l0aCh0b2xvd2VyKQ0KICANCmFfbmFtZSA8LSBwYXN0ZTAoImFfIiwgdG9sb3dlcihzaGVldCkpDQphX2RmIDwtIGRmICU+JQ0KICBkcGx5cjo6c2VsZWN0KHN0YXJ0c193aXRoKCdhJyksIGFsbF90cmlhbHNfc2VsZWN0KSAlPiUgDQogIGRwbHlyOjpyZW5hbWUodGVtcCA9IGFfdGVtcCkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGFjcm9zcyhzdGFydHNfd2l0aCgnYScpLCBhcy5udW1lcmljKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IHN0YXJ0c193aXRoKCdhJyksICMgU2VsZWN0IGFsbCBjb2x1bW5zIHRvIHBpdm90DQogICAgbmFtZXNfdG8gPSBjKCJjaGFtYmVyX2lkIiwgIi52YWx1ZSIpLCAjIFNlcGFyYXRlIGNvbHVtbiBuYW1lcyBpbnRvICdpZCcgYW5kIG90aGVyIHZhcmlhYmxlcw0KICAgIG5hbWVzX3NlcCA9ICJfIg0KICApICU+JQ0KICBkcGx5cjo6bXV0YXRlKHJlc3Bpcm9tZXRlcl9ncm91cCA9ICJhIikgIyBBZGQgYSBuZXcgY29sdW1uIHdpdGggYSBmaXhlZCB2YWx1ZQ0KDQpzbG9wZV9saXN0W1thX25hbWVdXTwtIGFfZGYNCg0KYl9uYW1lIDwtIHBhc3RlMCgiYl8iLCB0b2xvd2VyKHNoZWV0KSkNCmJfZGYgPC0gZGYgJT4lIA0KICBkcGx5cjo6c2VsZWN0KHN0YXJ0c193aXRoKCdiJyksIGFsbF90cmlhbHNfc2VsZWN0KSAlPiUgDQogIGRwbHlyOjpyZW5hbWUodGVtcCA9IGJfdGVtcCkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGFjcm9zcyhzdGFydHNfd2l0aCgnYicpLCBhcy5udW1lcmljKSkgJT4lIA0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IHN0YXJ0c193aXRoKCdiJyksICMgU2VsZWN0IGFsbCBjb2x1bW5zIHRvIHBpdm90DQogICAgbmFtZXNfdG8gPSBjKCJjaGFtYmVyX2lkIiwgIi52YWx1ZSIpLCAjIFNlcGFyYXRlIGNvbHVtbiBuYW1lcyBpbnRvICdpZCcgYW5kIG90aGVyIHZhcmlhYmxlcw0KICAgIG5hbWVzX3NlcCA9ICJfIg0KICApICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHJlc3Bpcm9tZXRlcl9ncm91cCA9ICJiIikNCg0Kc2xvcGVfbGlzdFtbYl9uYW1lXV0gPC0gYl9kZg0KDQpjX25hbWUgPC0gcGFzdGUwKCJjXyIsIHRvbG93ZXIoc2hlZXQpKQ0KY19kZiA8LSBkZiAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc3RhcnRzX3dpdGgoJ2MnKSwgYWxsX3RyaWFsc19zZWxlY3QpICU+JSANCiAgZHBseXI6OnJlbmFtZSh0ZW1wID0gY190ZW1wLA0KICAgICAgICAgICAgICAgIGlfY3ljbGUgPSBjeWNsZSkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGFjcm9zcyhzdGFydHNfd2l0aCgnYycpLCBhcy5udW1lcmljKSkgJT4lDQogIHBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gc3RhcnRzX3dpdGgoJ2MnKSwgIyBTZWxlY3QgYWxsIGNvbHVtbnMgdG8gcGl2b3QNCiAgICBuYW1lc190byA9IGMoImNoYW1iZXJfaWQiLCAiLnZhbHVlIiksICMgU2VwYXJhdGUgY29sdW1uIG5hbWVzIGludG8gJ2lkJyBhbmQgb3RoZXIgdmFyaWFibGVzDQogICAgbmFtZXNfc2VwID0gIl8iDQogICkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocmVzcGlyb21ldGVyX2dyb3VwID0gImMiKSAlPiUgDQogIGRwbHlyOjpyZW5hbWUoY3ljbGUgPSBpX2N5Y2xlKQ0KDQpzbG9wZV9saXN0W1tjX25hbWVdXSA8LSBjX2RmDQoNCmRfbmFtZSA8LSBwYXN0ZTAoImRfIiwgdG9sb3dlcihzaGVldCkpDQpkX2RmIDwtIGRmICU+JSANCiAgZHBseXI6OnNlbGVjdChzdGFydHNfd2l0aCgnZCcpLCBhbGxfdHJpYWxzX3NlbGVjdCkgJT4lIA0KICBkcGx5cjo6cmVuYW1lKHRlbXAgPSBkX3RlbXAsDQogICAgICAgICAgICAgICAgaV9kYXRlID0gZGF0ZSkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGFjcm9zcyhzdGFydHNfd2l0aCgnZCcpLCBhcy5udW1lcmljKSkgJT4lDQogIHBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gc3RhcnRzX3dpdGgoJ2QnKSwgIyBTZWxlY3QgYWxsIGNvbHVtbnMgdG8gcGl2b3QNCiAgICBuYW1lc190byA9IGMoImNoYW1iZXJfaWQiLCAiLnZhbHVlIiksICMgU2VwYXJhdGUgY29sdW1uIG5hbWVzIGludG8gJ2lkJyBhbmQgb3RoZXIgdmFyaWFibGVzDQogICAgbmFtZXNfc2VwID0gIl8iDQogICkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocmVzcGlyb21ldGVyX2dyb3VwID0gImQiKSAlPiUgDQogIGRwbHlyOjpyZW5hbWUoZGF0ZSA9IGlfZGF0ZSkNCg0Kc2xvcGVfbGlzdFtbZF9uYW1lXV0gPC0gZF9kZg0KfQ0KDQoNCnNsb3BlX2RmIDwtIGJpbmRfcm93cyhzbG9wZV9saXN0KSAlPiUgDQogIGRwbHlyOjptdXRhdGUocmVzcF9jYXRfZGF0ZSA9IHBhc3RlMChyZXNwaXJvbWV0ZXJfZ3JvdXAsICJfIiwgc3RhcnRfZGF0ZSksDQogICAgICAgICAgICAgICAgY2hhbWJlcl9uID0gc3RyX2V4dHJhY3QoY2hhbWJlcl9pZCwgIlxcZCsiKSwNCiAgICAgICAgICAgICAgICBpZF9wcm94ID0gcGFzdGUwKHJlc3BfY2F0X2RhdGUsICJfIiwgY2hhbWJlcl9uKSwNCiAgICAgICAgICAgICAgICB0aW1lX2htcyA9IGFzX2htcyh0aW1lKjM2MDApLA0KICAgICAgICAgICAgICAgIGRhdGVfY2hyID0gZm9ybWF0KGRhdGUsICIlZC8lbS8lWSIpDQogICAgICAgICAgICAgICAgKQ0KYGBgDQoNCiMjIE1ldGFkYXRhDQoNCioqbWV0YWRhdGEqKjogVGhpcyBpcyB0aGUgbWV0YSBkYXRhIGZvciBlYWNoIGNoYW1iZXIgPGJyPg0KDQoqTm90ZTogV2UgYXJlIGFsc28gYWRkaW5nIHZvbHVtZSBiYXNlZCBvbiBjaGFtYmVyIHR5cGUuKg0KDQpgYGB7cn0NCnNldHdkKG1ldGFfZmlsZXNfd2QpDQoNCm1ldGFkYXRhIDwtIHJlYWRfZXhjZWwoIk1vcnBoby54bHN4IiwgbmEgPSAiTkEiKSAlPiUgDQogIGRwbHlyOjptdXRhdGUoaWRfc3BsaXQgPSBpZCkgJT4lIA0KICB0aWR5cjo6c2VwYXJhdGUoaWRfc3BsaXQsIGludG8gPSBjKCJyZXNwaXJvbWV0ZXJfZ3JvdXAiLCAic2FsaW5pdHlfZ3JvdXAiLCAic3RhcnRfZGF0ZSIsICJjaGFtYmVyIiksIHNlcCA9ICJfIikgJT4lIA0KICBkcGx5cjo6bXV0YXRlKA0KICAgICAgdm9sdW1lID0gZHBseXI6OmNhc2Vfd2hlbigNCiAgICAgICAgY2hhbWJlcl90eXBlID09ICJMIiB+IDAuMzAwLA0KICAgICAgICBjaGFtYmVyX3R5cGUgPT0gIk1fTSIgfiAwLjEwNSwNCiAgICAgICAgY2hhbWJlcl90eXBlID09ICJNX05NIiB+IDAuMTEsDQogICAgICAgIGNoYW1iZXJfdHlwZSA9PSAiUyIgfiAwLjA1OCwNCiAgICAgICAgY2hhbWJlcl90eXBlID09ICJTTSIgfiAwLjA3NSwNCiAgICAgICAgY2hhbWJlcl90eXBlID09ICJEMyIgfiAwLjA1NSwNCiAgICAgICAgVFJVRSB+IE5BDQogICAgICApLA0KICAgICAgaWRfcHJveCA9IHBhc3RlMChyZXNwaXJvbWV0ZXJfZ3JvdXAsICJfIiwgc3RhcnRfZGF0ZSwgIl8iLCBjaGFtYmVyKSkNCmBgYA0KDQoNCiMjIyBDb21iaW5kaW5nIG1ldGFkYXRhDQoNCkFkZGluZyB0aGUgbWV0YSBkYXRhIHRvIExhYkNoYXJ0IHNsb3Blcw0KDQpgYGB7cn0NCnNsb3BlX2RmXzIgPC0gc2xvcGVfZGYgJT4lIA0KICBkcGx5cjo6c2VsZWN0KC1zdGFydF9kYXRlLCAtcmVzcGlyb21ldGVyX2dyb3VwKSAlPiUgDQogIGxlZnRfam9pbihtZXRhZGF0YSwgYnkgPSAiaWRfcHJveCIpICU+JSANCiAgZHBseXI6Om11dGF0ZShsaWdodF9kYXJrID0gaWZfZWxzZSh0aW1lX2htcyA+PSBhcy5obXMoIjA3OjAwOjAwIikgJiB0aW1lX2htcyA8IGFzLmhtcygiMTk6MDA6MDAiKSwgImxpZ2h0IiwgImRhcmsiKSkgJT4lIA0KICBkcGx5cjo6YXJyYW5nZShpZCkNCmBgYA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjDQojIERhdGENCiMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KIyMgTnVtYmVycyANCg0KV2UgaGF2ZSAqKjY0IGZpc2gqKiB3aXRoIE1PMiBkYXRhDQoNCmBgYHtyfQ0KbiA8LSBzbG9wZV9kZl8yICU+JSANCiAgZHBseXI6OmZpbHRlcihjaGFtYmVyX2NvbmRpdGlvbiA9PSAiZmlzaCIpICU+JSANCiAgZHBseXI6OmRpc3RpbmN0KGlkKSAlPiUgDQogIG5yb3coLikNCg0KcGFzdGUwKCJuID0gIiwgbikNCmBgYA0KPGJyPg0KV2l0aCA0OCBmcm9tIHRoZSAwIHBwdCBhbmQgNDggZnJvbSA5IHBwdCBncm91cHMNCg0KYGBge3J9DQpzbG9wZV9kZl8yICU+JSANCiAgZHBseXI6Omdyb3VwX2J5KHNhbGluaXR5X2dyb3VwKSAlPiUgDQogIGRwbHlyOjpyZWZyYW1lKCduIHRvdGFsJyA9IGxlbmd0aCh1bmlxdWUoaWQpKSkgJT4lIA0KICBndCgpICU+JSANCiAgY29sc19sYWJlbCgNCiAgICBzYWxpbml0eV9ncm91cCA9ICJTYWxpbml0eSBncm91cCINCiAgKSAlPiUgDQogIGNvbHNfYWxpZ24oDQogICAgYWxpZ24gPSAiY2VudGVyIiwgDQogICAgY29sdW1ucyA9IGV2ZXJ5dGhpbmcoKQ0KICApDQpgYGANCiMjIFNpemUNCg0KSGVyZSB3ZSBjYWN1bGF0ZSB0aGUgbWVhbiBsZW5ndGggYW5kIHNpemUgb2YgZmlzaCB1c2VkIGluIHRoZSBleHBlcmltZW50LiANCg0KYGBge3J9DQptYXNzX2xlbmd0aCA8LSBzbG9wZV9kZl8yICU+JSANCiAgZHBseXI6Omdyb3VwX2J5KGlkKSAlPiUgDQogIGRwbHlyOjpzYW1wbGVfbigxKSAlPiUgDQogIGRwbHlyOjp1bmdyb3VwKCkgJT4lIA0KICBkcGx5cjo6cmVmcmFtZSh4X21hc3MgPSByb3VuZChtZWFuKG1hc3MsIG5hLnJtID0gVFJVRSksIDMpLA0KICAgICAgICAgICAgICAgICBtaW5fbWFzcyA9IHJvdW5kKG1pbihtYXNzLCBuYS5ybSA9IFRSVUUpLCAzKSwNCiAgICAgICAgICAgICAgICAgbWF4X21hc3MgPSByb3VuZChtYXgobWFzcywgbmEucm0gPSBUUlVFKSwgMyksDQogICAgICAgICAgICAgICAgIHhfbGVuZ3RoID0gcm91bmQobWVhbihsZW5ndGgsIG5hLnJtID0gVFJVRSksIDIpLA0KICAgICAgICAgICAgICAgICBtaW5fbGVuZ3RoID0gcm91bmQobWluKGxlbmd0aCwgbmEucm0gPSBUUlVFKSwgMiksDQogICAgICAgICAgICAgICAgIG1heF9sZW5ndGggPSByb3VuZChtYXgobGVuZ3RoLCBuYS5ybSA9IFRSVUUpLCAyKSkNCg0KbWFzc19tZWFuIDwtIG1hc3NfbGVuZ3RoICU+JSANCiAgcHVsbCh4X21hc3MpDQoNCm1hc3NfbWluIDwtIG1hc3NfbGVuZ3RoICU+JSANCiAgcHVsbChtaW5fbWFzcykNCg0KbWFzc19tYXggPC0gbWFzc19sZW5ndGggJT4lIA0KICBwdWxsKG1heF9tYXNzKQ0KDQpsZW5ndGhfbWVhbiA8LSBtYXNzX2xlbmd0aCAlPiUgDQogIHB1bGwoeF9sZW5ndGgpDQoNCmxlbmd0aF9taW4gPC0gbWFzc19sZW5ndGggJT4lIA0KICBwdWxsKG1pbl9sZW5ndGgpDQoNCmxlbmd0aF9tYXggPC0gbWFzc19sZW5ndGggJT4lIA0KICBwdWxsKG1heF9sZW5ndGgpDQoNCnBhc3RlMCgiVGhlIG1lYW4gbWFzcyBvZiBmaXNoIHdhcyAiLCBtYXNzX21lYW4sICIgZyAocmFuZ2U6ICIsIG1hc3NfbWluLCAi4oCTIiwgbWFzc19tYXgsICIpIiwNCiAgICAgICAiLCBhbmQgdGhlIG1lYW4gbGVuZ3RoIHdhcyAiLCBsZW5ndGhfbWVhbiwgIiBtbSAocmFuZ2U6ICIsIGxlbmd0aF9taW4sICLigJMiLCBsZW5ndGhfbWF4LCAiKSIpDQpgYGANCg0KIyMgRmlsdGVyaW5nIHRyaWFscw0KDQpXZSB3aWxsIHJlbW92ZSA2IHRyaWFscyB3aGljaCBoYWQgZXJyb3JzLiBUaGVzZSBhcmUgYXMgZm9sbG93czogPGJyPg0KDQotCWFfMF8yNW5vdl8zIG5lZWRzIHRvIGJlIHJlbW92ZWQgKGZpc2ggZGllZCkNCi0JYl8wXzI2bm92XzQgZmxhdCBsaW5lZCBlYXJseQ0KLQljXzBfMjJub3ZfMiBhY2NpZGVudGFsbHkgb3BlbmVkIHRoZSBjaGFtYmVyIGVhcmx5DQotCWNfOV8yNm5vdl8yIHN0b3BwZWQgdHJpYWwgZWFybHksIHRvb2sgdG9vIGxvbmcNCi0JY185XzI2bm92XzQgc3RvcHBlZCB0cmlhbCBlYXJseSwgdG9vayB0b28gbG9uZw0KLQlkXzlfMjdub3ZfMyBzZW5zb3Igd2FzIGp1bXB5IGFuZCBlbmQgcG9pbnRzIHdlcmUgaGFyZCB0byBjb25maWRlbnRseSBJRCB2aXN1YWxseSA8YnI+DQoNCg0KYGBge3J9DQpyZW1vdmVfdHJpYWxfZXJyb3IgPC0gYygiYV8wXzI1bm92XzMiLCAiYl8wXzI2bm92XzQiLCAiY18wXzIybm92XzIiLCAiY185XzI2bm92XzIiLCAiY185XzI2bm92XzQiLCAiZF85XzI3bm92XzMiKQ0KDQpzbG9wZV9kZl9maWx0ZXIgPC0gc2xvcGVfZGZfMiAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoIShpZCAlaW4lIHJlbW92ZV90cmlhbF9lcnJvcikpDQpgYGANCg0KV2Ugbm93IGhhdmUgKio1OCBmaXNoKiogd2l0aCBNTzIgZGF0YQ0KDQpgYGB7cn0NCm4gPC0gc2xvcGVfZGZfZmlsdGVyICU+JSANCiAgZHBseXI6OmZpbHRlcihjaGFtYmVyX2NvbmRpdGlvbiA9PSAiZmlzaCIpICU+JSANCiAgZHBseXI6OmRpc3RpbmN0KGlkKSAlPiUgDQogIG5yb3coLikNCg0KcGFzdGUwKCJuID0gIiwgbikNCmBgYA0KDQpXaXRoIDQ1IGluIHRoZSAwIHBwdCBncm91cCBhbmQgNDUgaW4gdGhlIDkgcHB0IGdyb3VwIA0KDQpgYGB7cn0NCnNsb3BlX2RmX2ZpbHRlciAlPiUgDQogIGRwbHlyOjpncm91cF9ieShzYWxpbml0eV9ncm91cCkgJT4lIA0KICBkcGx5cjo6cmVmcmFtZSgnbiB0b3RhbCcgPSBsZW5ndGgodW5pcXVlKGlkKSkpICU+JSANCiAgZ3QoKSAlPiUgDQogIGNvbHNfbGFiZWwoDQogICAgc2FsaW5pdHlfZ3JvdXAgPSAiU2FsaW5pdHkgZ3JvdXAiDQogICkgJT4lIA0KICBjb2xzX2FsaWduKA0KICAgIGFsaWduID0gImNlbnRlciIsIA0KICAgIGNvbHVtbnMgPSBldmVyeXRoaW5nKCkNCiAgKQ0KYGBgDQoNCiMjIEZpbHRlcmluZyBNTzIgZXN0aW1hdGVzDQoNCkhlcmUgd2UgYXBwbHkgdGhlIGZvbGxvd2luZyBmaWx0ZXJzIHRvIHRoZSBNTzIgZGF0YTogPGJyPg0KDQotIFJlbW92ZSB0aGUgZmlyc3QgNSBTTVIgY3ljbGVzIChidXJuIGluKQ0KLSBSZW1vdmUgYWxsIHBvc2l0aXZlIHJhdyBzbG9wZXMNCi0gUmVtb3ZlIGFsbCBNTzIgY2FsY3VsYXRlZCB1c2luZyBsZXNzIHRoZW4gNjAgZGF0YSBwb2ludHMgKDUgbWluKSANCi0gUmVtb3ZlIGFsbCBNTzIgY2FsY3VsYXRlZCBpZiBvMiBpbmNyZWFzZXMgaW4gYSBjbG9zZWQgcGhhc2UgKGkuZS4gdHJpYWwgaGFzIGVuZGVkKSA8YnI+DQoNCmBgYHtyfQ0KY3ljbGVfYnVybiA8LSAwOjQNCg0Kc2xvcGVfZGZfZmlsdGVyXzEgPC0gc2xvcGVfZGZfZmlsdGVyICU+JQ0KICBkcGx5cjo6ZmlsdGVyKCEoY3ljbGUgJWluJSBjeWNsZV9idXJuKSAmIA0KICAgICAgICAgICAgICAgICAgbW8yY29yciA8IDAgJiANCiAgICAgICAgICAgICAgICAgIG4gPiA2MCAmDQogICAgICAgICAgICAgICAgICBjaGFtYmVyX2NvbmRpdGlvbiA9PSAiZmlzaCINCiAgICAgICAgICAgICAgICApDQoNCiMgTm93IHdlIHJlbW92ZSB0aGUgcG9pbnRzIGFmdGVyIHRoZSBjaGFtYmVyIGlzIG9wZW5lZA0KIyBUaGlzIGlzIGEgZnVuY3Rpb24gdG8gZG8gc28NCmZpbHRlcl9vMl9pbmNyZWFzZSA8LSBmdW5jdGlvbihncm91cCkgew0KICBncm91cCA8LSBncm91cCAlPiUNCiAgICBtdXRhdGUobzJfZGlmZiA9IG8yIC0gbGFnKG8yKSkgIyBDYWxjdWxhdGUgdGhlIGRpZmZlcmVuY2UgaW4gJ28yJw0KICANCiAgIyBGaW5kIHRoZSBmaXJzdCBpbmRleCB3aGVyZSAnbzJfZGlmZicgZXhjZWVkcyAxDQogIGN1dG9mZl9pbmRleCA8LSB3aGljaChncm91cCRvMl9kaWZmID4gMSlbMV0NCiAgDQogICMgRmlsdGVyIHRoZSBkYXRhIHVwIHRvIHRoZSBjdXRvZmYgaW5kZXgsIG9yIHJldHVybiB0aGUgZnVsbCBncm91cCBpZiBubyBjdXRvZmYNCiAgaWYgKCFpcy5uYShjdXRvZmZfaW5kZXgpKSB7DQogICAgZ3JvdXAgPC0gZ3JvdXBbMTooY3V0b2ZmX2luZGV4IC0gMSksIF0NCiAgfQ0KICANCiAgcmV0dXJuKGdyb3VwKQ0KfQ0KICANCiMgQXBwbHkgdGhlIGZ1bmN0aW9uIHRvIGVhY2ggZ3JvdXAgb2YgJ2NoYW1iZXJfaWQnDQpzbG9wZV90aWR5X2Nsb3NlZCA8LSBzbG9wZV9kZl9maWx0ZXJfMSAlPiUgDQogIGRwbHlyOjpmaWx0ZXIocGhhc2UgIT0gInNtciIpICU+JQ0KICBncm91cF9ieShpZCkgJT4lDQogIGdyb3VwX3NwbGl0KCkgJT4lDQogIGxhcHBseShmaWx0ZXJfbzJfaW5jcmVhc2UpICU+JQ0KICBiaW5kX3Jvd3MoKSAlPiUNCiAgc2VsZWN0KC1vMl9kaWZmKQ0KDQpzbG9wZV90aWR5X3NtciA8LSBzbG9wZV9kZl9maWx0ZXJfMSAlPiUgDQogIGRwbHlyOjpmaWx0ZXIocGhhc2UgPT0gInNtciIpDQoNCnNsb3BlX2RmX2ZpbHRlcl8yIDwtIHJiaW5kKHNsb3BlX3RpZHlfc21yLCBzbG9wZV90aWR5X2Nsb3NlZCkgJT4lIA0KICBkcGx5cjo6YXJyYW5nZShpZCwgb3JkZXIpDQpgYGANCg0KIyMgQ2FsY3VsYXRpbmcgU01SDQoNCldlIGhhdmUgZXN0aW1hdGVkIFNNUiB3aXRoIHR3byBkaWZmZXJlbnQgYXBwcmFjaGVzLiA8YnI+DQoNCkZpcnN0IHVzaW5nIHRoZSBtZWFuIG9mIHRoZSBsb3dlc3QgMyB2YWx1ZXMgKHNtcl8zbF9tZWFucykNCg0KYGBge3J9DQpzbXJfM2xfbWVhbnMgPC0gc2xvcGVfZGZfZmlsdGVyXzIgJT4lDQogIGRwbHlyOjpncm91cF9ieShpZCkgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKHBoYXNlID09ICJzbXIiKSAlPiUNCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhtbzJjb3JyKSkgJT4lDQogIGRwbHlyOjpzbGljZV9oZWFkKG4gPSAzKSAgJT4lICMgU2VsZWN0IHRoZSB0aHJlZSBsb3dlc3QgTU8yDQogIGRwbHlyOjp1bmdyb3VwKCkgJT4lIA0KICBkcGx5cjo6Z3JvdXBfYnkoaWQpICU+JSANCiAgZHBseXI6OnJlZnJhbWUoc21yX2wzID0gbWVhbihtbzJjb3JyKSkNCg0KIyBDb21iaW5lIHRoZSBwcm9jZXNzZWQgInNtciIgcGhhc2Ugd2l0aCBhbGwgb3RoZXIgcGhhc2VzDQpzbG9wZV9kZl9maWx0ZXJfMyA8LSBzbG9wZV9kZl9maWx0ZXJfMiAlPiUNCiAgZHBseXI6OmxlZnRfam9pbiguLCBzbXJfM2xfbWVhbnMsIGJ5ID0gImlkIikNCmBgYA0KDQo8YnI+DQpTZWNvbmQgdXNpbmcgdGhlIGNhbGNTTVIgZnVuY3Rpb24gYnkgQ2hhYm90LCBTdGVmZmVuc2VuIGFuZCBGYXJyZWxsICgyMDE2KSBET0k6IDEwLjExMTEvamZiLjEyODQ1LiBTcGVjaWZpY2FsbHksIFdlIHVzZSBtZWFuIG9mIHRoZSBsb3dlc3Qgbm9ybWFsIGRpc3RyaWJ1dGlvbiAoTUxORCkgd2hlcmUgQ1ZtbG5kIDwgNS40IGFuZCB0aGUgbWVhbiBvZiB0aGUgbG93ZXIgMjAlIHF1YW50aWxlIChxMC4yKSB3ZXJlIENWbWxuZCA+IDUuNC4gSWYgQ1ZtbG5kIGlzIG5vdCBjYWxjdWxhdGVkIHdlIGhhdmUgdXNlZCBxMC4yLiANCg0KYGBge3J9DQpsYWJjaGFydF9jaGFib3Rfc21yIDwtIHNsb3BlX2RmX2ZpbHRlcl8zICU+JQ0KICBkcGx5cjo6ZmlsdGVyKHBoYXNlID09ICJzbXIiKQ0KDQojIEV4dHJhY3QgZGlzdGluY3QgSURzDQppZHMgPC0gbGFiY2hhcnRfY2hhYm90X3NtciAlPiUgDQogIGRwbHlyOjpkaXN0aW5jdChpZCkgJT4lIA0KICBkcGx5cjo6cHVsbCgpDQoNCiMgSW5pdGlhbGlzZSBhbiBlbXB0eSBsaXN0IHRvIHN0b3JlIFNNUiBkYXRhDQpzbXJfbGlzdCA8LSBsaXN0KCkNCg0KIyBQcm9jZXNzIGVhY2ggSUQNCmZvciAoaWRfaSBpbiBpZHMpIHsNCiAgdHJ5Q2F0Y2goew0KICAgICMgRmlsdGVyIHRoZSBkYXRhIGZvciB0aGUgY3VycmVudCBJRA0KICAgIGRmX2kgPC0gbGFiY2hhcnRfY2hhYm90X3NtciAlPiUgDQogICAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICAgIGRwbHlyOjptdXRhdGUoYWJzX21vMmNvcnIgPSBhYnMobW8yY29ycikpDQogICAgDQogICAgIyBDYWxjdWxhdGUgU01SIHJlc3VsdHMNCiAgICBjYWxjU01SX3Jlc3VsdHMgPC0gY2FsY1NNUihkZl9pJGFic19tbzJjb3JyKQ0KICAgIENWbWxuZF9pIDwtIGNhbGNTTVJfcmVzdWx0cyRDVm1sbmQNCiAgICBxdWFudF9pIDwtIGNhbGNTTVJfcmVzdWx0cyRxdWFudCAlPiUgYXNfdGliYmxlKCkNCiAgICBxdWFudF8yMHBlcl9pIDwtIHF1YW50X2kkdmFsdWVbM10NCiAgICBtbG5kX2kgPC0gY2FsY1NNUl9yZXN1bHRzJG1sbmQNCiAgICBzbXJfdmFsdWUgPC0gaWZfZWxzZShDVm1sbmRfaSA8IDUuNCwgbWxuZF9pLCBxdWFudF8yMHBlcl9pKQ0KICAgIHNtcl90eXBlIDwtIGlmX2Vsc2UoQ1ZtbG5kX2kgPCA1LjQsICJtbG5kIiwgInF1YW50XzIwcGVyIikNCiAgICBzbXJfdmFsdWUgPC0gaWZfZWxzZShpcy5uYShzbXJfdmFsdWUpLCBxdWFudF8yMHBlcl9pLCBzbXJfdmFsdWUpDQogICAgc21yX3R5cGUgPC0gaWZfZWxzZShpcy5uYShzbXJfdHlwZSksICJxdWFudF8yMHBlciIsIHNtcl90eXBlKQ0KICAgIA0KICAgICMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBmb3IgdGhlIGN1cnJlbnQgSUQNCiAgICBzbXJfZGYgPC0gdGliYmxlKA0KICAgICAgaWQgPSBpZF9pLA0KICAgICAgc21yID0gc21yX3ZhbHVlLA0KICAgICAgc21yX2VzdCA9IHNtcl90eXBlDQogICAgKQ0KICAgIA0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICAjIEhhbmRsZSBlcnJvcnMgYnkgYXNzaWduaW5nIE5BIHZhbHVlcw0KICAgIHNtcl9kZiA8LSB0aWJibGUoDQogICAgICBpZCA9IGlkX2ksDQogICAgICBzbXIgPSBOQSwNCiAgICAgIHNtcl9lc3QgPSBOQQ0KICAgICkNCiAgfSkNCiAgDQogICMgQXBwZW5kIHRvIHRoZSBsaXN0DQogIHNtcl9saXN0W1tpZF9pXV0gPC0gc21yX2RmDQp9DQoNCiMgQ29tYmluZSBhbGwgaW5kaXZpZHVhbCBTTVIgZGF0YSBmcmFtZXMgaW50byBvbmUNCnNtcl9kZiA8LSBiaW5kX3Jvd3Moc21yX2xpc3QpICU+JSANCiAgZHBseXI6OnJlbmFtZShzbXJfY2hhYm90ID0gc21yLA0KICAgICAgICAgICAgICAgIHNtcl9jaGFib3RfbWV0aG9kID0gc21yX2VzdCkNCg0Kc2xvcGVfZGZfZmlsdGVyXzQgPC0gc2xvcGVfZGZfZmlsdGVyXzMgJT4lDQogIGRwbHlyOjpsZWZ0X2pvaW4oLiwgc21yX2RmLCBieSA9ICJpZCIpDQpgYGANCg0KIyMgVHJhbnNmb3JtaW5nIE1PMg0KDQpIZXJlIHdlIGFyZSB0cmFuc2Zvcm1pbmcgdGhlIE1PMiB1bml0cy4gVGhlIHJlc3VsdGluZyB2YXVsZXMgYXJlIGFzIGZvbGxvd3M6IA0KDQotIE1PMiA9IGFic29sdXRlIHZhbHVlIG9mIHRoZSBiYWNrZ3JvdW5kIGFuZCBsZWFrIGNvcnJlY3RlZCBtbzIgc2xvcGUgZnJvbSBMYWJjaGFydCAobW8yY29ycikgdGltZXMgdGhlIG5ldCB2b2x1bWUgb2YgdGhlIGNoYW1iZXIgKHZvbHVtZSAtIGZpc2ggbWFzcyksIHRpbWVzIDYwLCB0aW1lcyA2MCwgdG8gYWNoaWV2ZSBtZyBPMiAvIGguDQotIE1PMl9nID0gTU8yIGRpdmlkZWQgYnkgZmlzaCBtYXNzIHRvIGFjaGlldmUgbWcgTzIgLyBnLyBoIChpLmUuIG1hc3Mgc3RhbmRhcmRpc2VkKQ0KLSBTTVIgPSBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgbWVhbiBvZiB0aGUgdGhyZWUgbG93ZXN0IE1PMiBkdXJpbmcgdGhlIFNNUiBwaGFzZSAoc21yX2wzKSB0aW1lcyB0aGUgbmV0IHZvbHVtZSBvZiB0aGUgY2hhbWJlciAodm9sdW1lIC0gZmlzaCBtYXNzKSwgdGltZXMgNjAsIHRpbWVzIDYwLCB0byBhY2hpZXZlIG1nIE1PMiAvIGgNCi0gU01SX2cgPSBTTVIgZGl2aWRlZCBieSBmaXNoIG1hc3MNCi0gU01SX0NIQUJPVCA9IGFic29sdXRlIHZhbHVlIG9mIHRoZSBTTVIgZXN0aW1hdGVzIHVzaW5nIG1ldGhvZHMgZGVzY2liZWQgYnkgQ2hhYm90LCBTdGVmZmVuc2VuIGFuZCBGYXJyZWxsICgyMDE2KSAoc21yX2NoYWJvdCkNCi0gU01SX2cgPSBTTVJfQ0hBQk9UIGRpdmlkZWQgYnkgZmlzaCBtYXNzDQotIERPID0gZGlzc29sdmVkIG94eWdlbiBwZXJjZW50YWdlIGNhbGN1bGF0ZWQgZnJvbSBvMiB2YWx1ZXMgKG1nL0wpIHVzaW5nIHRoZSByZWNvcmRlZCB0ZW1wZXJhdHVyZSwgc2FsaW5pdHksIGFuZCBhIGNvbnN0YW50IGF0bW9zcGhlcmljIHByZXNzdXJlICgxMDEzLjI1KQ0KDQoNCmBgYHtyfQ0KIyBDb21iaW5lIGJhY2sgaW50byBvbmUgZGF0YSBmcmFtZQ0Kc2xvcGVfdGlkeSA8LSBzbG9wZV9kZl9maWx0ZXJfNCAlPiUgDQogICAgZHBseXI6Om11dGF0ZShETyA9IGNvbnZfbzIoDQogICAgICAgICAgICAgICAgICAgbzIgPSBvMiwNCiAgICAgICAgICAgICAgICAgICBmcm9tID0gIm1nX3Blcl9sIiwNCiAgICAgICAgICAgICAgICAgICB0byA9ICJwZXJjZW50X2Eucy4iLA0KICAgICAgICAgICAgICAgICAgIHRlbXAgPSB0ZW1wLCAjQw0KICAgICAgICAgICAgICAgICAgIHNhbCA9IG1lYXN1cmVkX3NhbGluaXR5LA0KICAgICAgICAgICAgICAgICAgIGF0bV9wcmVzID0gMTAxMy4yNSksDQogICAgICAgICAgICAgICAgICBvMl9rcGEgPSBjb252X28yKA0KICAgICAgICAgICAgICAgICAgIG8yID0gbzIsDQogICAgICAgICAgICAgICAgICAgZnJvbSA9ICJtZ19wZXJfbCIsDQogICAgICAgICAgICAgICAgICAgdG8gPSAia1BhIiwNCiAgICAgICAgICAgICAgICAgICB0ZW1wID0gdGVtcCwgI0MNCiAgICAgICAgICAgICAgICAgICBzYWwgPSBtZWFzdXJlZF9zYWxpbml0eSwNCiAgICAgICAgICAgICAgICAgICBhdG1fcHJlcyA9IDEwMTMuMjUpLA0KICAgICAgICAgICAgICAgICAgbmV0X3ZvbHVtZSA9IHZvbHVtZSAtIG1hc3MvMTAwMCwNCiAgICAgICAgICAgICAgICAgIE1PMiA9IGFicyhtbzJjb3JyKSpuZXRfdm9sdW1lKjYwKjYwLA0KICAgICAgICAgICAgICAgICAgTU8yX2cgPSBNTzIvbWFzcywNCiAgICAgICAgICAgICAgICAgIFNNUiA9IGFicyhzbXJfbDMpKm5ldF92b2x1bWUqNjAqNjAsDQogICAgICAgICAgICAgICAgICBTTVJfZyA9IFNNUi9tYXNzLA0KICAgICAgICAgICAgICAgICAgU01SX0NIQUJPVCA9IGFicyhzbXJfY2hhYm90KSpuZXRfdm9sdW1lKjYwKjYwLA0KICAgICAgICAgICAgICAgICAgU01SX0NIQUJPVF9nID0gU01SX0NIQUJPVC9tYXNzDQogICAgICAgICAgICAgICAgICApDQpgYGANCg0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBWaXN1YWxpc2luZyBkYXRhDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KDQojIyMgTzIgdnMgTU8yDQoNCkhlcmUgd2UgcGxvdCBhbGwgb3h5Z2VuIGNvbnN1bXB0aW9uIChNTzI7IG1nIE8yL2cvaCkgYnkgZGlzc29sdmVkIG94eWdlbiBwZXJjZW50YWdlIChETykgZm9yIGFsbCBmaXNoLCBpbmNsdWRpbmcgYWxsIFNNUiBlc3RpbWF0ZXMuIA0KDQpgYGB7cn0NCnNsb3BlX3RpZHkgJT4lIA0KICBnZ3Bsb3QoYWVzKHkgPSBNTzJfZywgeCA9IERPLCBjb2xvdXIgPSBpZCkpICsgIyBEZWZhdWx0IGFlc3RoZXRpY3MNCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGdlb21fc21vb3RoKGFlcyhncm91cCA9IGlkKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3VyID0gc2NhbGVzOjphbHBoYSgiYmxhY2siLCAwLjUpKSArICMgVHJhbnNwYXJlbnQgYmxhY2sgbGluZXMNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIikgKyAjIE92ZXJhbGwgc21vb3RoIGxpbmUNCiAgZ2VvbV9zbW9vdGgoc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICB0aGVtZV9jbGVhbigpICsNCiAgbGFicygNCiAgICBzdWJ0aXRsZSA9ICJBbGwgdmFsdWVzIiwNCiAgICB4ID0gIkRpc3NvbHZlZCBveHlnZW4gcGVyY2VudGFnZSAoRE8pIiwNCiAgICB5ID0gIk1PMiAobWcgTzIgZy9oKSINCiAgKQ0KYGBgDQo8YnI+DQoNClNhbWUgcGxvdCBidXQgd2l0aG91dCBTTVIgdmFsdWVzLiANCg0KYGBge3J9DQpzbG9wZV90aWR5ICU+JSANCiAgZHBseXI6OmZpbHRlcihwaGFzZSAhPSAic21yIikgJT4lIA0KICBnZ3Bsb3QoYWVzKHkgPSBNTzJfZywgeCA9IERPLCBjb2xvdXIgPSBpZCkpICsgIyBEZWZhdWx0IGFlc3RoZXRpY3MNCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGdlb21fc21vb3RoKGFlcyhncm91cCA9IGlkKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3VyID0gc2NhbGVzOjphbHBoYSgiYmxhY2siLCAwLjUpKSArICMgVHJhbnNwYXJlbnQgYmxhY2sgbGluZXMNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIikgKyAjIE92ZXJhbGwgc21vb3RoIGxpbmUNCiAgZ2VvbV9zbW9vdGgoc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICB0aGVtZV9jbGVhbigpICsNCiAgbGFicygNCiAgICBzdWJ0aXRsZSA9ICJPbmx5IGNsb3NlZCBwZXJpb2RzIiwNCiAgICB4ID0gIkRpc3NvbHZlZCBveHlnZW4gcGVyY2VudGFnZSAoRE8pIiwNCiAgICB5ID0gIk1PMiAoTzIgbWcvZy9oKSINCiAgKQ0KYGBgDQoNCiMjIyMgU2FsaW5pdHkNCjxicj4NCkxvb2tpbmcgYXQgdGhlIGRpZmZlcmVuY2UgcmVzcG9uc2VzIGluIHRoZSB0d28gc2FsaW5pdHkgZ3JvdXBzLiBJdCdzIGFwcGVhcnMgbW9yZSB2YXJpYWJsZSBpbiBmcmVzaHdhdGVyLg0KDQpgYGB7cn0NCnNsb3BlX3RpZHkgJT4lIA0KICBnZ3Bsb3QoYWVzKHkgPSBNTzJfZywgeCA9IERPLCBjb2xvdXIgPSBpZCkpICsgIyBEZWZhdWx0IGFlc3RoZXRpY3MNCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGdlb21fc21vb3RoKGFlcyhncm91cCA9IGlkKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3VyID0gc2NhbGVzOjphbHBoYSgiYmxhY2siLCAwLjUpKSArICMgVHJhbnNwYXJlbnQgYmxhY2sgbGluZXMNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIikgKyAjIE92ZXJhbGwgc21vb3RoIGxpbmUNCiAgZ2VvbV9zbW9vdGgoc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICB0aGVtZV9jbGVhbigpICsNCiAgZmFjZXRfd3JhcCh+c2FsaW5pdHlfZ3JvdXApICsNCiAgbGFicygNCiAgICBzdWJ0aXRsZSA9ICJtbzIgdnMgbzIgYnkgc2FsaW5pdHkgdHJlYXRtZW50IiwNCiAgICB4ID0gIkRpc3NvbHZlZCBveHlnZW4gcGVyY2VudGFnZSAoRE8pIiwNCiAgICB5ID0gIk1PMiAoTzIgbWcvZy9oKSINCiAgKQ0KYGBgDQoNCiMjIyMgQ2hhbWJlcnMNCjxicj4NCkxvb2tpbmcgYXQgdGhlIGRpZmZlcmVuY2UgY2hhbWJlciB0eXBlcw0KDQpgYGB7cn0NCnNsb3BlX3RpZHkgJT4lIA0KICBnZ3Bsb3QoYWVzKHkgPSBNTzJfZywgeCA9IERPLCBjb2xvdXIgPSBpZCkpICsgIyBEZWZhdWx0IGFlc3RoZXRpY3MNCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGdlb21fc21vb3RoKGFlcyhncm91cCA9IGlkKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3VyID0gc2NhbGVzOjphbHBoYSgiYmxhY2siLCAwLjUpKSArICMgVHJhbnNwYXJlbnQgYmxhY2sgbGluZXMNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIikgKyAjIE92ZXJhbGwgc21vb3RoIGxpbmUNCiAgZ2VvbV9zbW9vdGgoc2UgPSBUUlVFLCBjb2xvdXIgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICB0aGVtZV9jbGVhbigpICsNCiAgZmFjZXRfd3JhcCh+Y2hhbWJlcl90eXBlLCBzY2FsZSA9ICJmcmVlIikgKw0KICBsYWJzKA0KICAgIHN1YnRpdGxlID0gIm1vMiB2cyBvMiBieSBjaGFtYmVyIHR5cGUiLA0KICAgIHggPSAiRGlzc29sdmVkIG94eWdlbiBwZXJjZW50YWdlIChETykiLA0KICAgIHkgPSAiTU8yIChtZyBPMiBnL2gpIg0KICApDQpgYGANCg0KIyMjIyBVcmJpbmEgZXQgYWwuICgyMDEyKSBDb21wYXJpc29uDQoNCkNvbXBhcmlzb24gdG8gZGF0YSBmcm9tIFVyYmluYSBldCBhbC4gKDIwMTIpIA0KDQpgYGB7cn0NCm4gPC0gc2xvcGVfdGlkeSAlPiUgDQogIGRwbHlyOjpkaXN0aW5jdChpZCkgJT4lIA0KICBucm93KC4pDQoNCg0KbWluX28yX2twYSA8LSBtaW4oc2xvcGVfdGlkeSRvMl9rcGEsIG5hLnJtID0gVFJVRSkNCm1heF9vMl9rcGEgPC0gbWF4KHNsb3BlX3RpZHkkbzJfa3BhLCBuYS5ybSA9IFRSVUUpDQoNCnNsb3BlX3RpZHkgPC0gc2xvcGVfdGlkeSAlPiUNCiAgbXV0YXRlKG8yX2dyb3VwID0gY3V0KG8yX2twYSwgDQogICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEobWluX28yX2twYSwgbWF4X28yX2twYSwgbGVuZ3RoLm91dCA9IDEzKSwgIyAxMSBpbnRlcnZhbHMsIHNvIDEyIGJyZWFrcG9pbnRzDQogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBwYXN0ZTAoIkdyb3VwICIsIDE6MTIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGUubG93ZXN0ID0gVFJVRSkpDQoNCnRpbWVfYmluX2RmIDwtIHNsb3BlX3RpZHkgJT4lIA0KICBkcGx5cjo6Z3JvdXBfYnkobzJfZ3JvdXApICU+JSANCiAgZHBseXI6OnJlZnJhbWUobWVhbl9NTzJfZyA9IG1lYW4oTU8yX2cpKjMxLjI1LA0KICAgICAgICAgICAgICAgICBtZWFuX28yX2twYSA9IG1lYW4obzJfa3BhKSwNCiAgICAgICAgICAgICAgICAgbiA9IGxlbmd0aChNTzJfZykqMzEuMjUsDQogICAgICAgICAgICAgICAgIE1PMl9nX3NkID0gc2QoTU8yX2cpKjMxLjI1LA0KICAgICAgICAgICAgICAgICBvMl9rcGFfc2QgPSBzZChvMl9rcGEpKQ0KDQp0aW1lX2Jpbl9kZiAlPiUgDQogIGdncGxvdChhZXMoeSA9IG1lYW5fTU8yX2csIHggPSBtZWFuX28yX2twYSkpICsNCiAgIyBBZGQgcmF3IGRhdGEgcG9pbnRzDQogIGdlb21fcG9pbnQoZGF0YSA9IHNsb3BlX3RpZHksIGFlcyh5ID0gTU8yX2cqMzEuMjUsIHggPSBvMl9rcGEpLCANCiAgICAgICAgICAgICBzaXplID0gMiwgY29sb3IgPSAiZ3JleSIsIGFscGhhID0gMC41KSArICAjIFJhdyBkYXRhIHBvaW50cw0KICAjIEFkZCBzdW1tYXJ5IHBvaW50cw0KICBnZW9tX3BvaW50KHNpemUgPSAzLCBjb2xvdXIgPSAiYmxhY2siLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogICMgQWRkIHZlcnRpY2FsIGVycm9yIGJhcnMNCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IG1lYW5fTU8yX2cgLSBNTzJfZ19zZCwgeW1heCA9IG1lYW5fTU8yX2cgKyBNTzJfZ19zZCksIA0KICAgICAgICAgICAgICAgIHdpZHRoID0gMC4xNSwgY29sb3VyID0gImJsYWNrIikgKw0KICAjIEFkZCBob3Jpem9udGFsIGVycm9yIGJhcnMNCiAgZ2VvbV9lcnJvcmJhcmgoYWVzKHhtaW4gPSBtZWFuX28yX2twYSAtIG8yX2twYV9zZCwgeG1heCA9IG1lYW5fbzJfa3BhICsgbzJfa3BhX3NkKSwgDQogICAgICAgICAgICAgICAgIGhlaWdodCA9IDAuNCwgY29sb3VyID0gImJsYWNrIikgKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLCANCiAgICAgICAgICAgICB5ID0gMTYsIA0KICAgICAgICAgICAgIGxhYmVsID0gcGFzdGUwKCJuID0gIiwgbiksIA0KICAgICAgICAgICAgIGhqdXN0ID0gMCwgdmp1c3QgPSAxLCBzaXplID0gNCkgKw0KICB0aGVtZV9jbGVhbigpICsNCiAgbGFicygNCiAgICBzdWJ0aXRsZSA9ICIiLA0KICAgIHggPSAiUE8yIChrUGEpIiwNCiAgICB5ID0gIk1PMiAodW1vbCBPMiBnL2gpIg0KICApICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTYpLCBicmVha3MgPSBzZXEoMCwgMTYsIGJ5ID0gMikpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMjIpLCBicmVha3MgPSBzZXEoMCwgMjIsIGJ5ID0gMikpICAjIEN1c3RvbSB5LWF4aXMgc2NhbGUNCmBgYA0KDQoNCiMjIyBTTVINCg0KTWFraW5nIGFuIFNNUiBvbmx5IGRhdGEgZnJhbWUgDQoNCmBgYHtyfQ0Kc2xvcGVfdGlkeV9zbXIgPC0gc2xvcGVfdGlkeSAlPiUgDQogIGRwbHlyOjpmaWx0ZXIocGhhc2UgPT0gInNtciIpDQpgYGANCg0KIyMjIyBTTVIgYnkgc2FpbGl0eSANCg0KUGxvdCBvZiBTTVIgYnkgc2FsaW5pdHkgdHJlYXRtZW50LiBUaGUgc21hbGwgcG9pbnRzIGFyZSB0aGUgb2JzZXJ2ZWQgdmFsdWVzLCB0aGUgc2hhZGVkIGFyZWEgYmVoaW5kIHRoZSBwb2ludHMgaXMgdGhlIGEga2VybmVsIGRlbnNpdHkgb2YgdGhlIG9ic2VydmVkIGRhdGEsIHRoZSBib3ggcGxvdCBvbiB0b3Agc2hvdyB0aGUgbWVkaWFuIGFuZCBpbnRlcnF1YXJ0aWxlIHJhbmdlIChJUVIpLiANCg0KYGBge3J9DQptZWFuX21vMl9zYWxpbml0eSA8LSBzbG9wZV90aWR5X3NtciAlPiUgDQogIGRwbHlyOjpncm91cF9ieShzYWxpbml0eV9ncm91cCkgJT4lIA0KICBkcGx5cjo6cmVmcmFtZShtZWFuX21vMiA9IG1lYW4oTU8yLCBuYS5ybSA9IFRSVUUpKQ0KDQpmaWdfaSA8LSBnZ3Bsb3QoKSArDQogICAgZ2VvbV92aW9saW4oZGF0YSA9IHNsb3BlX3RpZHlfc21yLCBhZXMoeCA9IHNhbGluaXR5X2dyb3VwLCB5ID0gTU8yLCBmaWxsID0gc2FsaW5pdHlfZ3JvdXApLCBjb2xvciA9IE5BLCBhbHBoYSA9IDAuMykgKw0KICBnZW9tX2ppdHRlcihkYXRhID0gc2xvcGVfdGlkeV9zbXIsIGFlcyh4ID0gc2FsaW5pdHlfZ3JvdXAsIHkgPSBNTzIsIGZpbGwgPSBzYWxpbml0eV9ncm91cCksDQogICAgICAgICAgICAgICAgICAgICAgIHNoYXBlID0gMjEsIHNpemUgPSAyLCBjb2xvciA9ICJibGFjayIsIGFscGhhID0gMC4yKSArDQogIGdlb21fYm94cGxvdChkYXRhID0gc2xvcGVfdGlkeV9zbXIsIGFlcyh4ID0gc2FsaW5pdHlfZ3JvdXAsIHkgPSBNTzIsIGZpbGwgPSBzYWxpbml0eV9ncm91cCksDQogICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMSwgYWxwaGEgPSAwLjUsIG91dGxpZXIuc2hhcGUgPSBOQSwgd2lkdGggPSAwLjMpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gbWVhbl9tbzJfc2FsaW5pdHksIA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gc2FsaW5pdHlfZ3JvdXAsIHkgPSBtZWFuX21vMiwgZmlsbCA9IHNhbGluaXR5X2dyb3VwKSwgDQogICAgICAgICAgICAgICAgc2l6ZSA9IDMsIGFscGhhID0gMC44LCBjb2xvdXIgPSAiYmxhY2siLCBzdHJva2UgPSAyKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiM0QjUzMjAiLCAiIzAwMDA4MCIpKSArICAjIEN1c3RvbSBmaWxsIGNvbG91cnMNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCIjNEI1MzIwIiwgIiMwMDAwODAiKSkgKw0KICB0aGVtZV9jbGVhbigpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIGxhYnMoDQogICAgc3VidGl0bGUgPSAiIiwNCiAgICB4ID0gIlNhbGluaXR5IGdyb3VwIChwcHQpIiwNCiAgICB5ID0gIk1PMiAobWcgTzIgZy9oKSINCiAgKQ0KDQpmaWdfaQ0KYGBgDQoNCg0KIyMjIyBTTVIgZm9yIGVhY2ggZmlzaA0KDQpQbG90dGluZyBNTzIgZXN0aW1hdGVzIGZvciBlYWNoIGZpc2guIFRoZSBkYXNoZWQgcmVkIGxpbmUgaXMgQ2hhYm90IFNNUiBtZXRob2RzLCBhbmQgdGhlIHNvbGlkIGxpbmUgaXMgdGhlIG1lYW4gb2YgdGhlIGxvd2VzdCAzIG1lYXN1cmVzIChleGNsdWRpbmcgdGhlIGZpcnN0IDUgY3ljbGVzKSA8YnI+DQoNCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoPSA0fQ0KIyBDcmVhdGUgb3V0cHV0IGRpcmVjdG9yeSBpZiBuZWVkZWQNCm91dHB1dF9maWdfc2xvcGVzX3dkIDwtIGZpbGUucGF0aChvdXRwdXRfZmlnX3dkLCAic2xvcGVzIikNCmlmICghZGlyLmV4aXN0cyhvdXRwdXRfZmlnX3Nsb3Blc193ZCkpIHsNCiAgZGlyLmNyZWF0ZShvdXRwdXRfZmlnX3Nsb3Blc193ZCkNCn0NCg0KaWRzIDwtIHNsb3BlX3RpZHkgJT4lIA0KICBkcGx5cjo6ZGlzdGluY3QoaWQpICU+JSANCiAgcHVsbChpZCkgJT4lIA0KICBhcy5saXN0KCkNCg0KTU8yX3Bsb3RfbGlzdCA8LSAgbGlzdCgpDQoNCiMgMSkgT3BlbiB0aGUgUERGIGRldmljZSBvbmNlDQpwZGYoDQogIGZpbGUgICA9IGZpbGUucGF0aChvdXRwdXRfZmlnX3Nsb3Blc193ZCwgImNvbWJpbmVkX3Nsb3Blcy5wZGYiKSwgDQogIHdpZHRoICA9IDgsIA0KICBoZWlnaHQgPSA2DQopDQoNCiMgMikgTG9vcCBvdmVyIElEcyBhbmQgY3JlYXRlIGVhY2ggcGxvdA0KZm9yIChpZF9pIGluIGlkcykgew0KICANCiAgc21yX2NoYWJvdCA8LSBzbG9wZV90aWR5ICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6c2xpY2UoMSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKFNNUl9DSEFCT1QpDQogIA0KICBzbXJfbDMgPC0gc2xvcGVfdGlkeSAlPiUgDQogICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKSAlPiUgDQogICAgZHBseXI6OnNsaWNlKDEpICU+JSANCiAgICBkcGx5cjo6cHVsbChTTVIpDQogIA0KICBwbG90IDwtIHNsb3BlX3RpZHkgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGdncGxvdChhZXMoeCA9IG8yLCB5ID0gTU8yKSkgKw0KICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IHNtcl9jaGFib3QsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImRhcmtyZWQiKSArDQogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gc21yX2wzLCBjb2xvciA9ICJkYXJrcmVkIikgKw0KICAgIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IHBoYXNlKSkgKw0KICAgIHRoZW1lX2NsZWFuKCkgKw0KICAgIGxhYnMoDQogICAgICBzdWJ0aXRsZSA9IHBhc3RlMChpZF9pLCAiIHNsb3BlcyIpLA0KICAgICAgeCA9ICJNZWFuIG8yIChtZ19wZXJfbCkiLA0KICAgICAgeSA9ICJhYnMobW8yKSAobWdfcGVyX2wpIg0KICAgICkNCiAgDQogICMgSW5zdGVhZCBvZiBzYXZpbmcgZWFjaCBwbG90IHNlcGFyYXRlbHksIGp1c3QgcHJpbnQgaXQNCiAgcHJpbnQocGxvdCkNCiAgDQogIE1PMl9wbG90X2xpc3RbW2lkX2ldXSA8LSBwbG90DQp9DQoNCiMgMykgQ2xvc2UgdGhlIFBERiBkZXZpY2UgKmFmdGVyKiB0aGUgbG9vcA0KZGV2Lm9mZigpDQoNCmZvciAocCBpbiBNTzJfcGxvdF9saXN0KSB7DQogIHByaW50KHApDQp9DQpgYGANCg0KDQojIyMjIyMjIyMjIyMjIyMjIw0KIyBBbmFseXNpcw0KIyMjIyMjIyMjIyMjIyMjIw0KDQojIyBTTVINCg0KIyMjIFNjYWxpbmcgcHJlZGljdG9ycw0KDQpIZXJlIHdlIHNjYWxlIG91ciBwcmVkaWN0b3JzIGZvciB0aGUgbW9kZWwgPGJyPg0KDQpgYGB7cn0NCmNlbnRlcl9saXN0IDwtIGMoInRlbXAiLCAib3JkZXIiLCAibWFzcyIpIA0KDQpzbG9wZV90aWR5X3NtciA8LSBzbG9wZV90aWR5X3NtciAlPiUNCiAgZHBseXI6Om11dGF0ZShhY3Jvc3MoYWxsX29mKGNlbnRlcl9saXN0KSwgfiBzY2FsZSgueCwgY2VudGVyID0gVFJVRSwgc2NhbGUgPSBGQUxTRSksDQogICAgICAgICAgICAgICAgICAgICAgIC5uYW1lcyA9ICJ7LmNvbH1feiIpLA0KICAgICAgICAgICAgICAgIGxpZ2h0X2RhcmtfYyA9IGlmX2Vsc2UobGlnaHRfZGFyayA9PSAibGlnaHQiLCAxLCAwKSkNCmBgYA0KDQojIyMgTW9kZWwgc3RydWN0dXJlDQoNCkhlcmUgd2Ugd2lsbCB1c2UgYSBCYXllc2lhbiBHZW5lcmFsaXNlZCBMaW5lYXIgTWl4ZWQgTW9kZWwgKEdMTU0pIHdpdGggYSBHYW1tYSBkaXN0cmlidXRpb24gYW5kIGEgbG9nIGxpbmssIHdoZXJlIHRoZSBzaGFwZSBwYXJhbWV0ZXIgKEspIGlzIGFsc28gbW9kZWxsZWQgYXMgYSBmdW5jdGlvbiBvZiBwcmVkaWN0b3JzLiBUaGlzIG1vZGVscyBNTzIgYnkgc2FsaW5pdHkgZHVyaW5nIHRoZSBTTVIgcGhhc2UgdG8gc2VlIGlmIHRoZSBmaXNoIGhlbGQgYXQgZGlmZmVyZW50IHNhbGluaXRpZXMgaGF2ZSBkaWZmZXJlbnQgU01Scy4gV2UgaGF2ZSBhbHNvIGFkZGVkIGEgZmV3IHNjYWxlZCBwcmVkaWN0b3JzLCB0aGF0IG1heSBoZWxwIGRlc2NyaWJlIHZhcmlhdGlvbiBpbiB0aGUgZGF0YSwgc3VjaCBhcyBtYXNzIChnOyAwLjIx4oCTMS42KSB0ZW1wZXJhdHVyZSAowrBDOyAxMy44NDHigJMxNC4yNzcpLCBtZWFzdXJlbWVudCBvcmRlciAoMeKAkzI4KSwgYW5kIGxpZ2h0L2RhcmsgY3ljbGUgKGxpZ2h0IG9yIGRhcms7IGxpZ2h0IGJldHdlZW4gMDc6MDA6MDAgYW5kIDE5OjAwOjAwKSwgd2UgYWxzbyBpbmNsdWRlIGEgcmFuZG9tIGVmZmVjdCBmb3IgZmlzaCBpZCB0byBhY2NvdW50IGZvciBtdWx0aXBsZSBNTzIgbWVhc3VyZXMgb24gZWFjaCBmaXNoLiBXZSBhbGxvd2VkIHRoZSB0aGUgc2hhcGUgcGFyYW1ldGVyIChLKSB0byB2YXJ5IGFzIGEgZnVuY3Rpb24gb2Ygc29tZSBvZiB0aGUgcHJlZGljdG9ycyAoZS5nLiBzYWxpbml0eV9ncm91cCwgb3JkZXJfeikgdG8gaW1wcm92ZSBmaXQuIDxicj4NCg0KYGBge3J9DQpzbXJfZ2FtbWFfYmYgPC0gYmYoTU8yIH4gdGVtcF96ICsgDQogICAgICAgICAgICAgICAgICBvcmRlcl96ICArIA0KICAgICAgICAgICAgICAgICAgbGlnaHRfZGFya19jICsgDQogICAgICAgICAgICAgICAgICBtYXNzX3ogKyANCiAgICAgICAgICAgICAgICAgIHNhbGluaXR5X2dyb3VwICsgKDF8aWQpLA0KICAgICAgICAgICAgICAgIHNoYXBlIH4gc2FsaW5pdHlfZ3JvdXAgKyANCiAgICAgICAgICAgICAgICAgIG9yZGVyX3osDQogICAgICAgICAgICAgICAgZmFtaWx5ID0gR2FtbWEobGluayA9ICJsb2ciKSkNCmBgYA0KDQoNCiMjIyBQcmlvciBzZWxlY3Rpb24NCg0KVGhlc2UgYXJlIHRoZSBkZWZhdWx0IHByaW9ycy4gV2Ugd2lsbCB1c2UgdGhlc2UuDQoNCmBgYHtyfQ0Kc3VwcHJlc3NXYXJuaW5ncyhnZXRfcHJpb3Ioc21yX2dhbW1hX2JmLCBkYXRhID0gc2xvcGVfdGlkeV9zbXIsIGZhbWlseSA9IEdhbW1hKGxpbmsgPSAibG9nIikpKQ0KYGBgDQoNCiMjIyBSdW4gbW9kZWwNCg0KSGVyZSB3ZSBydW4gdGhlIG1vZGxlLCBJIGhhdmUgaGFzZWQgdGhpcyBvdXQgYmVjYXVzZSBJIGhhdmUgc2F2ZWQgdGhlIG1vZGVsIGZvciBxdWljayByZWxvYWRpbmcuIA0KDQpgYGB7cn0NCiMgc2V0d2Qob3V0cHV0X21vZHNfd2QpDQojIA0KIyBzbXJfbW9kX2dhbW1hIDwtIGJybShzbXJfZ2FtbWEsDQojICAgICAgICAgICAgICAgIGRhdGEgPSBzbG9wZV90aWR5X3NtciwNCiMgICAgICAgICAgICAgICAgY29yZXMgPSA0LA0KIyAgICAgICAgICAgICAgICBjaGFpbnMgPSA0LA0KIyAgICAgICAgICAgICAgICB3YXJtdXAgPSAxMDAwLA0KIyAgICAgICAgICAgICAgICBzZWVkID0gMTQzMDE5LA0KIyAgICAgICAgICAgICAgICB0aGluID0gMiwNCiMgICAgICAgICAgICAgICAgaXRlciA9IDgwMDAsDQojICAgICAgICAgICAgICAgIHNhdmVfcGFycyA9IHNhdmVfcGFycyhhbGw9VFJVRSksDQojICAgICAgICAgICAgICAgIHNhbXBsZV9wcmlvciA9IFRSVUUsDQojICAgICAgICAgICAgICAgIGZpbGUgPSAnc21yX21vZF9nYW1tYScpDQojIHByaW50KCJNb2RlbCBjb21wbGV0ZSIpDQpgYGANCg0KSGVyZSB3ZSByZWxvYWQgdGhlIG1vZGVsIDxicj4NCg0KYGBge3J9DQpzZXR3ZChvdXRwdXRfbW9kc193ZCkNCg0Kc21yX21vZF9nYW1tYSA8LSAgcmVhZFJEUyhmaWxlID0gInNtcl9tb2RfZ2FtbWEucmRzIikNCmBgYA0KDQoNCkNoZWNraW5nIG1vZGVsIGNvbnZlcmdlbmNlIDxicj4NCg0KYGBge3J9DQpwbG90KHNtcl9tb2RfZ2FtbWEsIGFzayA9IEYpDQpgYGANCg0KQ2hlY2tpbmcgcmhhdCBhcmUgZXF1YWwgdG8gb25lIDxicj4NCg0KYGBge3J9DQp0aWR5KHJoYXQoc21yX21vZF9nYW1tYSkpICU+JSANCiAgZHBseXI6OnJlbmFtZShyaGF0ID0geCkNCmBgYA0KDQpVc2luZyBsZWF2ZSBvbmUgb3V0IChsb28pIG1lYXN1cmUgb2YgZml0LCB0aGUgbW9kZWwgYXBwZWFycyB0byBwcmVmb3JtIHdlbGwsIGFsbCBQYXJldG8gayBlc3RpbWF0ZXMgYXJlIGdvb2QgKGsgPCAwLjcpIDxicj4NCg0KYGBge3J9DQpsb28oc21yX21vZF9nYW1tYSkNCmBgYA0KDQpNb2RlbCBwcmVkaWN0aW9ucyBnZW5lcmFsbHkgYWxpZ24gd2l0aCB0aGUgb2JzZXJ2ZWQgZGF0YSA8YnI+DQoNCmBgYHtyfQ0KcGxvdCA8LSBwcF9jaGVjayhzbXJfbW9kX2dhbW1hLCB0eXBlID0gImRlbnNfb3ZlcmxheSIpDQpwbG90IA0KYGBgDQoNCg0KIyMjIFJlc3VsdHMNCg0KV2UgZGlkIG5vdCBzZWUgYSBtZWFuaW5nZnVsIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgU01SIG1lYXN1cmVtZW50cyBvZiBmaXNoIGZyb20gdGhlIHR3byBzYWxpbml0eSB0cmVhdG1lbnRzLiANCg0KIyMjIyBUYWJsZSAxIA0KDQpUYWJsZSAxOiBGaXhlZCBlZmZlY3QgRXN0aW1hdGVzICjOsikgYW5kIDk1JSBDcmVkaWJsZSBJbnRlcnZhbHMgKDk1JSBDSSkgZnJvbSBhIA0KDQpgYGB7cn0NCm1vZGVsX2VzdCA8LSBmaXhlZihzbXJfbW9kX2dhbW1hLCBwcm9icyA9IGMoMC4wMjUsIDAuOTc1KSkgJT4lIA0KICBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiUHJlZGljdG9yIikgJT4lIA0KICBkcGx5cjo6bXV0YXRlKCfOsicgPSByb3VuZChFc3RpbWF0ZSwgMyksDQogICAgICAgICAgICAgICAgUTIuNSA9IHJvdW5kKFEyLjUsIDMpLA0KICAgICAgICAgICAgICAgIFE5Ny41ID0gcm91bmQoUTk3LjUsIDMpLA0KICAgICAgICAgICAgICAgICc5NSUgQ0knID0gcGFzdGUwKCJbIiwgUTIuNSwgIiwgIiwgUTk3LjUsICJdIikpDQoNCm1vZGVsX2VzdCAlPiUgDQogIGRwbHlyOjpzZWxlY3QoUHJlZGljdG9yLCAnzrInLCAnOTUlIENJJykgJT4lIA0KICBndCgpDQpgYGANCg0KTG9va2luZyBhdCB0aGUgbWFyZ2luYWwgbWVhbiBkaWZmZXJlbmNlIGJldHdlZW4gc2FsaW5pdHkgZ3JvdXBzDQoNCmBgYHtyfQ0KZW1fcmVzdWx0cyA8LSBlbW1lYW5zKHNtcl9tb2RfZ2FtbWEsIH4gc2FsaW5pdHlfZ3JvdXApIA0KY29udHJhc3RfcmVzdWx0cyA8LSBjb250cmFzdChlbV9yZXN1bHRzLCBtZXRob2QgPSAicGFpcndpc2UiKQ0KZW1fcmVzdWx0c19kZiA8LSAgZW1fcmVzdWx0cyAlPiUgdGlkeSgpICU+JSANCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfiBleHAoLikpKQ0KY29udHJhc3RfcmVzdWx0c19kZiA8LSAgY29udHJhc3RfcmVzdWx0cyAlPiUgdGlkeSgpICU+JSANCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfiBleHAoLikpKQ0KDQoNCmVtX3Jlc3VsdHNfZGYgJT4lIA0KICBndCgpDQpgYGANCg0KYGBge3J9DQplbW1lYW5zX2RyYXdzIDwtIHNtcl9tb2RfZ2FtbWEgJT4lDQogIGVtbWVhbnMofiBzYWxpbml0eV9ncm91cCkgJT4lDQogIGdhdGhlcl9lbW1lYW5zX2RyYXdzKCkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKC52YWx1ZSA9ICBleHAoLnZhbHVlKSwNCiAgICAgICAgICAgICAgICBzYWxpbml0eV9ncm91cCA9IGFzLmNoYXJhY3RlcihzYWxpbml0eV9ncm91cCkpDQoNCmVtbWVhbnNfY29udHJhc3RfZHJhd3MgPC0gIHNtcl9tb2RfZ2FtbWEgJT4lDQogIGVtbWVhbnMofiBzYWxpbml0eV9ncm91cCkgJT4lDQogIGNvbnRyYXN0KG1ldGhvZCA9ICJwYWlyd2lzZSIpICU+JQ0KICBnYXRoZXJfZW1tZWFuc19kcmF3cygpICU+JSANCiAgZHBseXI6Om11dGF0ZSgudmFsdWUgPSAgZXhwKC52YWx1ZSkpDQoNCnJvdW5kX3ZhcnMgPSBjKCIudmFsdWUiLCAiLmxvd2VyIiwgIi51cHBlciIpDQoNCnNtcl9lbW1lYW5zIDwtIGVtbWVhbnNfZHJhd3MgJT4lIA0KICBtZWFuX3FpKC53aWR0aCA9IGMoLjg4OSwgMC45NDkpKSAlPiUgDQogIG11dGF0ZShhY3Jvc3MoYWxsX29mKHJvdW5kX3ZhcnMpLCB+IHJvdW5kKC54LCBkaWdpdHMgPSAzKSkpDQoNCnNtcl9jb250cmFzdCA8LSBlbW1lYW5zX2NvbnRyYXN0X2RyYXdzICU+JSANCiAgbWVhbl9xaSgud2lkdGggPSBjKC44ODksIDAuOTQ5KSkgJT4lIA0KICBtdXRhdGUoYWNyb3NzKGFsbF9vZihyb3VuZF92YXJzKSwgfiByb3VuZCgueCwgZGlnaXRzID0gMykpKQ0KDQpzbXJfZW1tZWFucw0Kc21yX2NvbnRyYXN0DQpgYGANCg0KIyMjIyBGaWcgMQ0KDQojIyMjIERpc2N1c3MgLSB3aGF0IGRhdGEgdG8gcGxvdCBhbmQgd2hhdCBmb3JtYXRlDQoNCioqRmlndXJlIDE6KiogVGhlIE1PMiAobWcgbzIgZy9oKSBkdXJpbmcgU01SIG1lYXNzdXJtZW50cyBwbG90dGVkIGJ5IHNhbGluaXR5IHRyZWF0bWVudC4gVGhlIHNtYWxsIHRyYW5zcGFyZW50IHBvaW50cyBhcmUgdGhlIG9ic2VydmVkIHZhbHVlcywgdGhlIHNoYWRlZCBhcmVhIGJlaGluZCB0aGUgcG9pbnRzIGlzIHRoZSBhIGtlcm5lbCBkZW5zaXR5IG9mIHRoZSBvYnNlcnZlZCBkYXRhLCB0aGUgbGFyZ2UgY29sb3VyZWQgcG9pbnQgKHRvIHRoZSByaWdodCkgaXMgdGhlIG9ic2VydmVkIG1lYW4sIHRoZSBsYXJnZSBncmV5IHBvaW50IHdpdGggZXJyb3IgYmFycyAodG8gdGhlIGxlZnQpIGlzIHRoZSBtb2RlbCBlc3RpbWF0ZWQgbWFyZ2luYWwgbWVhbiAoZWVtZWFuKSA5NSUgQ3JlZGlibGUgSW50ZXJ2YWxzICg5NSUgQ0kpLiANCg0KYGBge3J9DQptZWFuX21vMl9zYWxpbml0eSA8LSBzbG9wZV90aWR5X3NtciAlPiUgDQogIGRwbHlyOjpncm91cF9ieShzYWxpbml0eV9ncm91cCkgJT4lIA0KICBkcGx5cjo6cmVmcmFtZShtZWFuX21vMiA9IG1lYW4oTU8yLCBuYS5ybSA9IFRSVUUpKQ0KDQoNCmZpZ18xIDwtIGdncGxvdCgpICsNCiAgZ2VvbV92aW9saW4oZGF0YSA9IHNsb3BlX3RpZHlfc21yLA0KICAgICAgICAgICAgICBhZXMoeCA9IHNhbGluaXR5X2dyb3VwLCB5ID0gTU8yLCBmaWxsID0gc2FsaW5pdHlfZ3JvdXApLA0KICAgICAgICAgICAgICBjb2xvciA9IE5BLCBhbHBoYSA9IDAuMikgKw0KICBnZW9tX2ppdHRlcihkYXRhID0gc2xvcGVfdGlkeV9zbXIsDQogICAgICAgICAgICAgIGFlcyh4ID0gc2FsaW5pdHlfZ3JvdXAsIHkgPSBNTzIsIGZpbGwgPSBzYWxpbml0eV9ncm91cCksDQogICAgICAgICAgICAgIHNoYXBlID0gMjEsIHdpZHRoID0gMC4zLCBzaXplID0gMSwgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuMSkgKw0KICAgIGdlb21fcG9pbnQoZGF0YSA9IG1lYW5fbW8yX3NhbGluaXR5LA0KICAgICAgICAgICAgIGFlcyh4ID0gc2FsaW5pdHlfZ3JvdXAsIHkgPSBtZWFuX21vMiwgZmlsbCA9IHNhbGluaXR5X2dyb3VwKSwNCiAgICAgICAgICAgICBzaXplID0gNCwgYWxwaGEgPSAxLCBzdHJva2UgPSAyLCBjb2xvciA9ICJibGFjayIsIHNoYXBlID0gMjEsDQogICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMDUpKSArDQogIHN0YXRfcG9pbnRpbnRlcnZhbChkYXRhID0gZW1tZWFuc19kcmF3cywgDQogICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IHNhbGluaXR5X2dyb3VwLCB5ID0gLnZhbHVlKSwNCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJncmV5IiwgcG9pbnRfaW50ZXJ2YWwgPSAibWVhbl9xaSIsIC53aWR0aCA9IDAuOTUsIHNoYXBlID0gMjEsICBzdHJva2UgPSAyLCBwb2ludF9zaXplID0gNCwgYWxwaGEgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9udWRnZSh4ID0gLTAuMDUpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiM0QjUzMjAiLCAiIzAwMDA4MCIpKSArICAjIEN1c3RvbSBmaWxsIGNvbG91cnMNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCIjNEI1MzIwIiwgIiMwMDAwODAiKSkgKw0KICB0aGVtZV9jbGVhbigpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIGxhYnMoDQogICAgc3VidGl0bGUgPSAiIiwNCiAgICB4ID0gIlNhbGluaXR5IGdyb3VwIChwcHQpIiwNCiAgICB5ID0gIk1PMiAobWcgTzIgZy9oKSINCiAgKQ0KDQpmaWdfMQ0KYGBgDQoNCg0KIyMgSW5jcmVtZW50YWwgcmVncmVzc2lvbiBhbmFseXNlcw0KDQpIZXJlIHdlIGFyZSBmb2xsb3dpbmcgdGhlIG1ldGhvZHMgVXJiaW5hIGV0IGFsLiAoMjAxMikgd2l0aCBhbiBpbmNyZW1lbnRhbCByZWdyZXNzaW9uIGFuYWx5c2VzLCBpbiBvcmRlciB0byBkZXRlcm1pbmUgdGhlIGJlc3QgZml0IGZvciB0aGUgZGF0YS48YnI+DQoNClRoaXMgYW5hbHlzaXMgZXZhbHVhdGVkIGVhY2ggcG9seW5vbWlhbCBvcmRlciBlcXVhdGlvbiBzdGFydGluZyBhdCB6ZXJvIGFuZCB0aGVuIGluY3JlYXNpbmcgdG8gdGhlIHRoaXJkIG9yZGVyLiBUaGlzIHBlcm1pdHRlZCBhIG1hdGhlbWF0aWNhbCBhc3Nlc3NtZW50IG9mIHdoZXRoZXIgdGhlIGRhdGEgYmVzdCBmaXR0ZWQgYSBzaW5nbGUgbGluZWFyIHJlbGF0aW9uc2hpcCAoMHRoLW9yZGVyIHBvbHlub21pYWw7IHN1Z2dlc3RpbmcgdGhlIGZpc2ggd2VyZSBveHljb25mb3JtaW5nIGFuZCBkbyBub3QgcmVhY2ggYSBQY3JpdCksIG9yIHdoZXRoZXIgYSBQTzIgY3JpdCB2YWx1ZSBjb3VsZCBiZSBkZXRlcm1pbmVkIGFzIGFuIGludGVyc2VjdGlvbiBwb2ludCBvZiB0d28gZGlzdGluY3QgZnVuY3Rpb25zIChvbmUgYXQgaHlwb3hpYyBveHlnZW4gY29uY2VudHJhdGlvbnMsIHRoZSBvdGhlciBhdCBub3Jtb3hpYzsgaS5lLiBveHlyZWd1bGF0aW9uKS4gPGJyPg0KDQojIyMgQnVpbGRpbmcgQmF5ZXNpYW4gcmVncmVzc2lvbnMNCg0KSGVyZSB3ZSBhcmUgdXNpbmcgYSBCYXllc2lhbiBhcHByb2FjaCB0byBtb2RlbCBmaXR0aW5nIHdpdGggYnJtLiBUaGVzZSBtb2RlbHMgdGFrZSBhIGxvbmcgdGltZSB0byBydW4sIHNvIEkgaGF2ZSBzYXZlZCB0aGVtIGFuZCByZS1sb2FkZWQgdGhlbSB0byBzYXZlIHRpbWUuIEkgaGF2ZSBhbHNvIHNhdmVkIHRoZSBzdW1tYXJ5IGRhdGEgcHJvZHVjZWQgZnJvbSB0aGUgbW9kZWxzLCB0byBzYXZlIHRpbWUsIHlvdSBjYW4gc2ltcGx5IHNraXAgdGhlIGhhc2hlZCBjb2RlIGFuZCBpbnB1dCB0aGUgcmVzdWx0aW5nIHN1bW1hcnkgZGF0YS4gPGJyPg0KDQpXZSB3aWxsIHJ1biBvdXIgY3VzdG9tIGZ1bmN0aW9uLCAqKmJheWVzX2luY3JlbWVudGFsX3JlZ3Jlc3Npb25fYnlfaWQqKi4gKipUaGlzIGNvZGUgdGFrZXMgYSB3aGlsZSB0byBydW4qKi4gSWYgeW91IGhhdmUgYWxyZWFkeSBydW4gdGhpcyBvbmNlLCBvciBoYXZlIGRvd25sb2FkZWQgdGhlIHNhdmVkIG1vZGVscyBmcm9tIEdpdEh1YiAqKnNraXAgdGhpcyBzdGVwKiogKHRoYXQncyB3aHkgaXRzIGhhc2hlZCBvdXQpLCBhbmQgcnVuIHRoZSBuZXh0IGxpbmUsIHdoaWNoIGxvYWRzIHRoZSBtb2RlbHMuIDxicj4NCg0KYGBge3J9DQojIG91dHB1dF9tb2RzX2JheWVzX3dkIDwtIHBhc3RlMChvdXRwdXRfbW9kc193ZCwgIi4vYmF5ZXMtcmVncyIpDQojIGlmZWxzZSghZGlyLmV4aXN0cyhvdXRwdXRfbW9kc19iYXllc193ZCksIGRpci5jcmVhdGUob3V0cHV0X21vZHNfYmF5ZXNfd2QpLCAiRm9sZGVyIGFscmVhZHkgZXhpc3RzIikNCiMgDQojIGlkcyA8LSBzbG9wZV90aWR5ICU+JQ0KIyAgIGRwbHlyOjpkaXN0aW5jdChpZCkgJT4lDQojICAgcHVsbChpZCkNCiMgDQojIHBsYW4obXVsdGlzZXNzaW9uKQ0KIyANCiMgZnV0dXJlX21hcCgNCiMgICBpZHMsIA0KIyAgIGJheWVzX2luY3JlbWVudGFsX3JlZ3Jlc3Npb25fYnlfaWQsDQojICAgaWRfbmFtZSA9ICJpZCIsDQojICAgZGF0YSA9IHNsb3BlX3RpZHksDQojICAgcmVzcG9uc2UgPSAiTU8yX2ciLA0KIyAgIHByZWRpY3RvciA9ICJETyIsDQojICAgc2F2ZV9tb2RlbHMgPSBUUlVFLA0KIyAgIG1vZF9vdXRwdXRfd2QgPSBvdXRwdXRfbW9kc19iYXllc193ZA0KIyApDQojIA0KIyBwbGFuKHNlcXVlbnRpYWwpICANCmBgYA0KDQpMb2FkIGFsbCBtb2RlbHMgYW5kIHN0b3JlIGluIGEgbGlzdCwgd2lsbCB1c2UgYSBsb3Qgb2YgbWVtb3J5LiBZb3UgY2FuIGFsc28gc2tpcCB0aGlzIHN0ZXAgYW5kIGxvYWQgdGhlIHJldWxzdGluZyBkYXRhIGZyYW1lcyBiZWxvdy4gSSBhbSB1c2luZyB0aGUgY3VzdG9tIGZ1bmN0aW9uICoqbG9hZF9yZHMqKiwgc28gd2UgY2FuIGNvbXBhcmUgdGhlbSBhbmQgZ2VuZXJhdGUgcHJlZGljdGlvbnMuIDxicj4NCg0KYGBge3J9DQojIGJheWVzX3JlZ19tb2RzIDwtIGxvYWRfcmRzKG1vZGVsX2R3ID0gb3V0cHV0X21vZHNfYmF5ZXNfd2QpDQpgYGANCg0KIyMjIE1vZGVsIGZpdHMNCg0KR2V0IG1vZGVsIGZpdCBwYXJhbWV0ZXJzIGxvbyBhbmQgcjIgdXNpbmcgdGhlIGN1c3RvbSBmdW5jdGlvbiwgKippbmNyZW1lbnRhbF9yZWdyZXNzaW9uX2JheWVzX2ZpdHMqKi4gPGJyPg0KDQpgYGB7cn0NCiMgc2V0d2QobW9kX2RhdGFfd2QpDQojIGJheWVzX3JlZ19tb2RzX2ZpdCA8LSBpbmNyZW1lbnRhbF9yZWdyZXNzaW9uX2JheWVzX2ZpdHMobW9kZWxzID0gYmF5ZXNfcmVnX21vZHMpDQojIHdyaXRlLmNzdihiYXllc19yZWdfbW9kc19maXQsICJiYXllc19yZWdfbW9kc19maXQuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpgYGANCg0KUmVhZGluZyBpbiB0aGlzIG1vZGVsIGZpdCBkYXRhIGZyYW1lLCBpbiB0aGUgY2FzZSB5b3UgZGlkIG5vdCBsb2FkIGluIGFsbCB0aGUgbW9kZWxzLiA8YnI+IA0KDQpgYGB7cn0NCnNldHdkKG1vZF9kYXRhX3dkKQ0KYmF5ZXNfcmVnX21vZHNfZml0IDwtIHJlYWQuY3N2KCJiYXllc19yZWdfbW9kc19maXQuY3N2IikNCmBgYA0KDQpTZWxlY3RpbmcgdGhlIGJlc3QgZml0dGluZyBtb2RlbCA8YnI+DQoNCmVscGRfbG9vLCBvciB0aGUgZXhwZWN0ZWQgbG9nIHBvaW50d2lzZSBwcmVkaWN0aXZlIGRlbnNpdHkgZm9yIGxlYXZlLW9uZS1vdXQgY3Jvc3MtdmFsaWRhdGlvbiwgaXMgYSBtZXRyaWMgdXNlZCBpbiBCYXllc2lhbiBtb2RlbCBldmFsdWF0aW9uIHRvIGFzc2VzcyB0aGUgcHJlZGljdGl2ZSBhY2N1cmFjeSBvZiBhIG1vZGVsLiA8YnI+DQoNClRoZSBlbHBkX2xvbyBpcyBhbiBhcHByb3hpbWF0aW9uIG9mIGhvdyB3ZWxsIHRoZSBtb2RlbCBpcyBleHBlY3RlZCB0byBwcmVkaWN0IG5ldyBkYXRhLCBiYXNlZCBvbiBsZWF2ZS1vbmUtb3V0IGNyb3NzLXZhbGlkYXRpb24uIEhpZ2hlciBlbHBkX2xvbyB2YWx1ZXMgaW5kaWNhdGUgYmV0dGVyIHByZWRpY3RpdmUgcGVyZm9ybWFuY2UuIDxicj4NCg0KYGBge3J9DQpiZXN0X2ZpdF9iYXllc19yZWcgPC0gYmF5ZXNfcmVnX21vZHNfZml0ICU+JSANCiAgZHBseXI6Omdyb3VwX2J5KGlkKSAlPiUNCiAgZHBseXI6Om11dGF0ZShlbHBkX2xvb19yYW5rID0gcmFuaygtZWxwZF9sb28pKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QoaWQsIG1vZGVsX3R5cGUsIGVscGRfbG9vLCByMiwgZWxwZF9sb29fcmFuaywgcjJfcTIuNSwgcjJfcTk3LjUsIGVzdGltYXRlX0RPLCBjb25mLmxvd19ETywgY29uZi5oaWdoX0RPKQ0KYGBgDQoNCg0KIyMjIE1vZGVsIHByZWRpY3Rpb25zDQoNClB1bGxpbmcgb3VyIG1vZGVsIHByZWRpY3Rpb25zIHVzaW5nIGEgY3VzdG9tIGZ1bmN0aW9uICoqYmF5ZXNfbW9kX3ByZWRpY3Rpb25zKiouIDxicj4NCg0KYGBge3J9DQojc2V0d2QobW9kX2RhdGFfd2QpDQojYmF5ZXNfcmVnX21vZHNfcHJlZGljdGlvbnMgPC0gYmF5ZXNfbW9kX3ByZWRpY3Rpb25zKG1vZGVscyA9IGJheWVzX3JlZ19tb2RzLCBvcmlnaW5hbF9kYXRhID0gc2xvcGVfdGlkeSkNCiN3cml0ZS5jc3YoYmF5ZXNfcmVnX21vZHNfcHJlZGljdGlvbnMsICJiYXllc19yZWdfbW9kc19wcmVkaWN0aW9ucy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQpSZWFkaW5nIGluIHRoZSBwcmVkaWN0ZWQgZGF0YSA8YnI+DQoNCmBgYHtyfQ0Kc2V0d2QobW9kX2RhdGFfd2QpDQpiYXllc19yZWdfbW9kc19wcmVkaWN0aW9ucyA8LSByZWFkLmNzdigiYmF5ZXNfcmVnX21vZHNfcHJlZGljdGlvbnMuY3N2IikNCmBgYA0KDQpXZSBhcmUgZ29pbmcgdG8gY29tYmluZWQgdGhpcyB3aXRoIG91ciBiZXN0IGZpdHRpbmcgbW9kZWwgZGYsIHNvIHdlIGtub3cgaG93IHRoZXkgcmFua3MgZm9yIExPTy4gPGJyPg0KDQpgYGB7cn0NCmJheWVzX3JlZ19tb2RzX3ByZWRpY3Rpb25zIDwtIGZ1bGxfam9pbihiYXllc19yZWdfbW9kc19wcmVkaWN0aW9ucywgYmVzdF9maXRfYmF5ZXNfcmVnLCBieSA9IGMoImlkIiwgIm1vZGVsX3R5cGUiKSkNCmBgYA0KDQojIyMgTW9kZWwgc2VsZWN0aW9uIHN1bW1hcnkNCg0KVGhlIGJlc3QgZml0dGluZyBtb2RlbHMgd2VyZSBtb3N0IG9mdGVuIGEgMm5kLW9yZGVyIHBvbHlub21pYWwgKCpuKiA9IDIyLCAzOCUpIG9yIGEgM3JkLW9yZGVyIHBvbHlub21pYWwgKCpuKiA9IDE2LCAyOCUpLiBUaGlzIGNvdWxkIHN1Z2dlc3QgdGhlIHByZXNlbmNlIG9mIGEgY3JpdGljYWwgb3h5Z2VuIHRocmVzaG9sZCAoUGNyaXQpIHdoZXJlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBvMiBhbmQgTU8yIGNoYW5nZXMuIFRvIGNvbmZpcm0gdGhlaXIgaXMgYSBQY3JpdCwgd2UgbmVlZCB0byB2YWxpZGF0ZWQgdGhlIHNoYXBlIG9mIHRoZSBwb2x5bm9taWFscyBhbmQgaW4gc2hvdWxkIHVzZSBhIG1vcmUgc3BlY2lmaWMgbW9kZWwgdG8gdGVzdCB0aGUgUGNyaXQgdmFsdWUuIEluIGFueSBjYXNlLCBUaGlzIHR5cGUgb2YgbW9kZWwgaXMgaW5kaWNhdGl2ZSBvZiAqKm94eXJlZ3VsYXRvcioqLiA8YnI+DQoNClRoZSBuZXh0IG1vc3QgY29tbW9uIGFyZSAwdGgtb3JkZXIgYW5kIDFzdC1vcmRlciBwb2x5bm9taWFscyAoYm90aCAqbiogPSAxMCwgMTclKS4gSW4gdGhlIGNhc2Ugb2YgdGhlIDB0aC1vcmRlciBtb2RlbCwgaXQgc3VnZ2VzdHMgdGhhdCBNTzIgZG9lcyBub3Qgc2hvdyBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGVwZW5kZW5jZSBvbiB0aGUgbzIuIEluIG90aGVyIHdvcmRzLCB0aGUgbWV0YWJvbGljIHJhdGUgZG9lcyBub3QgYWRqdXN0IGJhc2VkIG9uIG94eWdlbiBhdmFpbGFiaWxpdHksIGFuZCB0aGVyZSBpcyBubyBjbGVhciBjcml0aWNhbCBveHlnZW4gdGhyZXNob2xkIChQY3JpdCkgd2hlcmUgdGhlIHJlbGF0aW9uc2hpcCBjaGFuZ2VzLiBUaGlzIGlzIGluZGljYXRpdmUgb2YgYSAqKm94eXJlZ3VsYXRvcioqLiBJbiB0aGUgY2FzZSBvZiB0aGUgMXN0LW9yZGVyIHBvbHlub21pYWxzLCBpdCBzdWdnZXN0IHRoZSBwcmVzZW5jZXMgb2YgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG8yIGFuZCBNTzIsIHdoaWNoIGlzIGluZGljYXRpdmUgb2YgKipveHljb25mb3JtZXIqKi4gSG93ZXZlciwgdG8gYmUgdHJ1ZSBldmlkZW5jZSBvZiBhIG94eWNvbmZvcm1lciB0aGlzIHJlbGF0aW9uc2hpcCBzaG91bGQgYmUgcG9zaXRpdmUgKGkuZS4gYXMgbzIgZmFsbHMgTU8yIGFsc28gZmFsbHMpLiBPbmx5IDIgb2YgdGhlIDEwIGluZGl2aWR1YWxzIGJlc3QgbW9kZWxsZWQgd2l0aCBhIGxpbmVhciBmdW5jdGlvbiBoYWQgcG9zaXRpdmUgZXN0aW1hdGVzIHdpdGggY3JlZGlibGUgaW50ZXJ2YWxzIHRoYXQgZGlkIG5vdCBvdmVybGFwIHdpdGggemVybyAoVGFibGUgMSkuIDxicj4NCg0KYGBge3J9DQpiZXN0X21vZCA8LSBiZXN0X2ZpdF9iYXllc19yZWcgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKGVscGRfbG9vX3JhbmsgPT0gMSkNCg0KdG90YWxfZmlzaCA8LSBucm93KGJlc3RfbW9kKQ0KDQp0YWJsZV9id20gPC0gYmVzdF9tb2QgJT4lIA0KICBkcGx5cjo6Z3JvdXBfYnkobW9kZWxfdHlwZSkgJT4lIA0KICBkcGx5cjo6cmVmcmFtZShuID0gbGVuZ3RoKGlkKSwNCiAgICAgICAgICAgICAgICAgcGVyY2VudCA9IHJvdW5kKChuL3RvdGFsX2Zpc2gpKjEwMCwyKSkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGJlc3RfbW9kZWxfbmFtZSA9IGNhc2Vfd2hlbigNCiAgICAgIG1vZGVsX3R5cGUgPT0gImxtXzAiIH4gIjB0aC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIG1vZGVsX3R5cGUgPT0gImxtXzEiIH4gIjFzdC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIG1vZGVsX3R5cGUgPT0gImxtXzIiIH4gIjJuZC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIG1vZGVsX3R5cGUgPT0gImxtXzMiIH4gIjNyZC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIFRSVUUgfiAiRVJST1IiDQogICAgKSkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KGJlc3RfbW9kZWxfbmFtZSwgZXZlcnl0aGluZygpLCAtbW9kZWxfdHlwZSkNCg0KDQp0YWJsZV9id20gJT4lIA0KICAgZ3QoKSAlPiUgDQogIGNvbHNfYWxpZ24oDQogICAgYWxpZ24gPSAiY2VudGVyIiwgDQogICAgY29sdW1ucyA9IGV2ZXJ5dGhpbmcoKQ0KICApDQpgYGANCg0KU3VtbWFyeSBvZiBmaXNoIGJlc3QgbW9kZWwgd2l0aCBhIGxpbmVhciBmdW5jdGlvbi4gPGJyPg0KDQpgYGB7cn0NCnRhYmxlX2xtXzEgPC0gYmVzdF9tb2QgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKG1vZGVsX3R5cGUgPT0gImxtXzEiKSAlPiUgDQogIGRwbHlyOjptdXRhdGUocl9zcV9jaSA9IHBhc3RlMChyb3VuZChyMiwgMyksICIgKCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQocjJfcTIuNSwgMyksICLigJMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHIyX3E5Ny41LCAzKSwgIikiKSwNCiAgICAgICAgICAgICAgICBlc3RfY2kgPSBwYXN0ZTAocm91bmQoZXN0aW1hdGVfRE8sIDYpLCAiICgiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoY29uZi5sb3dfRE8sIDYpLCAi4oCTIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKGNvbmYuaGlnaF9ETywgNiksICIpIiksDQogICAgICAgICAgICAgICAgY29uZm9ybWVyID0gaWZfZWxzZShjb25mLmxvd19ETyA+IDAsICJDb25mb3JtaW5nIiwgIk5vdCBjb25mb3JtaW5nIikpICU+JSANCiAgZHBseXI6OnNlbGVjdChpZCwgcl9zcV9jaSwgZXN0X2NpLCBjb25mb3JtZXIpICU+JSANCiAgZHBseXI6OmFycmFuZ2UoY29uZm9ybWVyKSAlPiUgDQogIGRwbHlyOjp1bmdyb3VwKCkNCg0KDQp0YWJsZV9sbV8xICU+JSANCiAgZ3QoKSAlPiUgDQogIGNvbHNfYWxpZ24oDQogICAgYWxpZ24gPSAiY2VudGVyIiwgDQogICAgY29sdW1ucyA9IGV2ZXJ5dGhpbmcoKQ0KICApICU+JSANCiAgY29sc19sYWJlbCgNCiAgICBpZCA9ICJGaXNoIElEIiwNCiAgICByX3NxX2NpID0gInIyIChDSSkiLA0KICAgIGVzdF9jaSA9ICJFc3RpbWF0ZSAoQ0kpIiwNCiAgICBjb25mb3JtZXIgPSAiRXZpZGVuY2Ugb2Ygb3h5Y29uZm9ybWluZyINCiAgKQ0KYGBgDQoNCiMjIyMgUGxvdGluZyBtb2RlbHMNCg0KTm93IHdlIGFyZSBwbG90dGluZyBlYWNoIG9mIHRoZSByZWdyZXNzaW9ucy4gRmlyc3QgbWFraW5nIGEgZGlyZWN0b3J5IHRvIHNhdmUgdGhlIGZpZ3VyZXMgPGJyPg0KDQpgYGB7cn0NCmluY3JlbWVudGFsX3JlZ19iYXllc193ZCA8LSBmaWxlLnBhdGgob3V0cHV0X2ZpZ193ZCwgImluY3JlbWVudGFsX3JlZ3Jlc3Npb25zLi9iYXllcyIpDQppZiAoIWRpci5leGlzdHMoaW5jcmVtZW50YWxfcmVnX2JheWVzX3dkKSkgew0KICBkaXIuY3JlYXRlKGluY3JlbWVudGFsX3JlZ19iYXllc193ZCkNCn0NCmBgYA0KDQpQbG90aW5nIGFsbCByZWdyZXNzaW9uLCBhbmQgaGlnaGxpZ2h0aW5nIHRoZSBtb2RlbCB0aGF0IGhhcyB0aGUgYmVzdCBmaXQsIGJhc2VkIG9uIEFJQyB2YWx1ZXMgPGJyPg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgbGlzdCB0byBzdG9yZSB0aGUgcGxvdHMNCnBsb3RzIDwtIGxpc3QoKQ0KbW9kZWxfcHJlZHNfbGlzdCA8LSBsaXN0KCkNCg0KZm9yIChpZF9pIGluIGlkcykgew0KICANCiAgIyBGaWx0ZXIgZGF0YSBmb3IgdGhlIGN1cnJlbnQgSUQNCiAgZGZfaSA8LSBiYXllc19yZWdfbW9kc19wcmVkaWN0aW9ucyAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKGxpbmVfc2l6ZSA9IGlmX2Vsc2UoZWxwZF9sb29fcmFuayA9PSAxLCAyLCAxKSwNCiAgICAgICAgICAgYWxwaGFfdmFsdWUgPSBpZl9lbHNlKGVscGRfbG9vX3JhbmsgPT0gMSwgMSwgMC40KSkNCiAgDQogIHhfbWluIDwtIGRmX2kgJT4lDQogICAgZHBseXI6OnJlZnJhbWUobWluID0gbWluKERPKSwgbmEucm0gPSBUUlVFKSAlPiUgDQogICAgZHBseXI6OnB1bGwobWluKQ0KICANCiAgeV9tYXggPC0gZGZfaSAlPiUNCiAgICBkcGx5cjo6cmVmcmFtZShtYXggPSBtYXgoTU8yX2cpLCBuYS5ybSA9IFRSVUUpICU+JSANCiAgICBkcGx5cjo6cHVsbChtYXgpDQogIA0KICBiZXN0X3dlaWdodGVkX21vZGVsX2kgPC0gYmVzdF9maXRfYmF5ZXNfcmVnICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kgJiBlbHBkX2xvb19yYW5rID09IDEpDQogIA0KICBwb2x5X2lfbmFtZSA8LSBiZXN0X3dlaWdodGVkX21vZGVsX2kgJT4lDQogICAgZHBseXI6Om11dGF0ZShuYW1lID0gY2FzZV93aGVuKA0KICAgICAgbW9kZWxfdHlwZSA9PSAibG1fMCIgfiAiMHRoLW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgbW9kZWxfdHlwZSA9PSAibG1fMSIgfiAiMXN0LW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgbW9kZWxfdHlwZSA9PSAibG1fMiIgfiAiMm5kLW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgbW9kZWxfdHlwZSA9PSAibG1fMyIgfiAiM3JkLW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgVFJVRSB+ICJFUlJPUiINCiAgICApKSAlPiUgDQogICAgZHBseXI6OnB1bGwobmFtZSkNCiAgDQogIHJfaSA8LSBiZXN0X3dlaWdodGVkX21vZGVsX2kgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocl9zcV9jaSA9IHBhc3RlMChyb3VuZChyMiwgMyksICIgKCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQocjJfcTIuNSwgMyksICLigJMiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvdW5kKHIyX3E5Ny41LCAzKSwgIikiKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHJfc3FfY2kpDQoNCiAgIyBDcmVhdGUgdGhlIHBsb3QNCiAgcCA8LSBnZ3Bsb3QoKSArDQogICAgZ2VvbV9yaWJib24oZGF0YSA9IGRmX2ksDQogICAgICAgICAgICAgICAgYWVzKHggPSBETywgeSA9IHByZWRpY3RlZCwgeW1pbiA9IHByZWRfbG93ZXIsIHltYXggPSBwcmVkX3VwcGVyLCBmaWxsID0gbW9kZWxfdHlwZSksIGFscGhhID0gMC4xKSArDQogICAgZ2VvbV9saW5lKGRhdGEgPSBkZl9pLCANCiAgICAgICAgICAgICAgYWVzKHggPSBETywgeSA9IHByZWRpY3RlZCwgY29sb3VyID0gbW9kZWxfdHlwZSwgc2l6ZSA9IGxpbmVfc2l6ZSwgYWxwaGEgPSBhbHBoYV92YWx1ZSkpICsNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBkZl9pICU+JSBkcGx5cjo6ZmlsdGVyKGVscGRfbG9vX3JhbmsgPT0gMSksIGFlcyh4ID0gRE8sIHkgPSBNTzJfZyksIGFscGhhID0gMC42LCBjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMikgKw0KICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygicmVkIiwgImJsdWUiLCAiZ3JlZW4iLCAicHVycGxlIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiMHRoIE9yZGVyIiwgIjFzdCBPcmRlciIsICIybmQgT3JkZXIiLCAiM3JkIE9yZGVyIikpICsNCiAgICBzY2FsZV9zaXplX2lkZW50aXR5KCkgKyAgIyBVc2UgdGhlIHNpemUgdmFsdWVzIGRpcmVjdGx5DQogICAgc2NhbGVfYWxwaGFfaWRlbnRpdHkoZ3VpZGUgPSAibm9uZSIpICsgICMgUmVtb3ZlIHRoZSBhbHBoYSBsZWdlbmQgDQogICAgYW5ub3RhdGUoInRleHQiLCB4ID0geF9taW4sIA0KICAgICAgICAgICAgIHkgPSB5X21heCwgDQogICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZTAoIkJlc3QgZml0OiAiLHBvbHlfaV9uYW1lLCAiXG4iLCAicjIgPSAiLCByX2kpLCANCiAgICAgICAgICAgICBoanVzdCA9IDAsIHZqdXN0ID0gMSwgc2l6ZSA9IDQpICsNCiAgICBsYWJzKA0KICAgICAgdGl0bGUgPSBwYXN0ZSgiTW9kZWwgRml0cyB2cyBSYXcgRGF0YSBmb3IgSUQiLCBpZF9pKSwNCiAgICAgIHggPSAiRGlzc29sdmVkIG94eWdlbiBwZXJjZW50YWdlIChETykiLA0KICAgICAgeSA9ICJNTzIgKG8yIG1nL2cvaCkiLA0KICAgICAgY29sb3VyID0gIk1vZGVsIikgKw0KICAgIHRoZW1lX2NsYXNzaWMoKQ0KICANCiAgIyBTdG9yZSB0aGUgcGxvdA0KICBwbG90c1tbaWRfaV1dIDwtIHANCiAgDQogIHByaW50KHApDQp9DQoNCg0KI1RvIHNhdmUgYWxsIHBsb3RzIHRvIGluZGl2aWR1YWwgZmlsZXMNCmZvciAoaWRfaSBpbiBpZHMpIHsNCiAgZ2dzYXZlKGZpbGVuYW1lID0gcGFzdGUwKGluY3JlbWVudGFsX3JlZ19iYXllc193ZCwgIi4vcGxvdF8iLCBpZF9pLCAiLnBuZyIpLCBwbG90ID0gcGxvdHNbW2lkX2ldXSwgd2lkdGggPSA4LCBoZWlnaHQgPSA2KQ0KfQ0KYGBgDQoNCiNIRVJFDQoNCmBgYHtyfQ0Kb3V0cHV0X21vZHNfYmF5ZXNfZ2xvYmFsX3dkIDwtIHBhc3RlMChvdXRwdXRfbW9kc193ZCwgIi4vYmF5ZXMtcmVncy1nbG9iYWwiKQ0KaWZlbHNlKCFkaXIuZXhpc3RzKG91dHB1dF9tb2RzX2JheWVzX2dsb2JhbF93ZCksIGRpci5jcmVhdGUob3V0cHV0X21vZHNfYmF5ZXNfZ2xvYmFsX3dkKSwgIkZvbGRlciBhbHJlYWR5IGV4aXN0cyIpDQpgYGANCg0KIyNOZWVkIG9ubHkgYmVzdCBmaXR0aW5nIG1vZGVsDQpIZXJlIHdlIGFyZSBncm91cGluZyBmaXNoIGJ5IGJlc3QgZml0dGluZyBtb2RlbCBhbmQgZ2V0dGluZyBhbiBhdmVyYWdlIHRyZW5kICANCg0KYGBge3J9DQojIA0KIyBiZXN0X2ZpdCA8LSBiYXllc19yZWdfbW9kc19wcmVkaWN0aW9ucyAlPiUgDQojICAgZHBseXI6OmZpbHRlcihlbHBkX2xvb19yYW5rID09IDEpDQojIA0KIyBpZHMgPC0gYmVzdF9maXQgJT4lDQojICAgZHBseXI6OmRpc3RpbmN0KG1vZGVsX3R5cGUpICU+JQ0KIyAgIHB1bGwobW9kZWxfdHlwZSkNCiMgDQojIHBsYW4obXVsdGlzZXNzaW9uKQ0KIyANCiMgZnV0dXJlX21hcCgNCiMgICBpZHMsDQojICAgYmF5ZXNfaW5jcmVtZW50YWxfcmVncmVzc2lvbl9ieV9pZCwNCiMgICBpZF9uYW1lID0gIm1vZGVsX3R5cGUiLA0KIyAgIGRhdGEgPSBiZXN0X2ZpdCwNCiMgICByZXNwb25zZSA9ICJNTzJfZyIsDQojICAgcHJlZGljdG9yID0gIkRPIiwNCiMgICBzYXZlX21vZGVscyA9IFRSVUUsDQojICAgbW9kX291dHB1dF93ZCA9IG91dHB1dF9tb2RzX2JheWVzX2dsb2JhbF93ZA0KIyApDQojIA0KIyBwbGFuKHNlcXVlbnRpYWwpDQpgYGANCg0KYGBge3J9DQpiYXllc19yZWdfbW9kcyA8LSBsb2FkX3Jkcyhtb2RlbF9kdyA9IG91dHB1dF9tb2RzX2JheWVzX2dsb2JhbF93ZCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBnZ3Bsb3QoKSArDQojICAgZ2VvbV9yaWJib24oZGF0YSA9IGRmX2ksDQojICAgICAgICAgICAgICAgYWVzKHggPSBETywgeSA9IHByZWRpY3RlZCwgeW1pbiA9IHByZWRfbG93ZXIsIHltYXggPSBwcmVkX3VwcGVyLCBmaWxsID0gbW9kZWxfdHlwZSksIGFscGhhID0gMC4xKSArDQojICAgZ2VvbV9saW5lKGRhdGEgPSBkZl9pLA0KIyAgICAgICAgICAgICBhZXMoeCA9IERPLCB5ID0gcHJlZGljdGVkLCBjb2xvdXIgPSBtb2RlbF90eXBlLCBzaXplID0gbGluZV9zaXplLCBhbHBoYSA9IGFscGhhX3ZhbHVlKSkgKw0KIyAgIGdlb21fcG9pbnQoZGF0YSA9IGJheWVzX3JlZ19tb2RzX3ByZWRpY3Rpb25zICU+JSBkcGx5cjo6ZmlsdGVyKGVscGRfbG9vX3JhbmsgPT0gMSksDQojICAgICAgICAgICAgICBhZXMoeCA9IERPLCB5ID0gTU8yX2cpLCBhbHBoYSA9IDAuMSwgY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDEpICsNCiMgICBnZW9tX2xpbmUoZGF0YSA9IGJheWVzX3JlZ19tb2RzX3ByZWRpY3Rpb25zICU+JSBkcGx5cjo6ZmlsdGVyKGVscGRfbG9vX3JhbmsgPT0gMSksDQojICAgICAgICAgICAgIGFlcyh4ID0gRE8sIHkgPSBwcmVkaWN0ZWQsIGJ5ID0gaWQpLCBhbHBoYSA9IDAuMikgKw0KIyAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygicmVkIiwgImJsdWUiLCAiZ3JlZW4iLCAicHVycGxlIiksDQojICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCIwdGggT3JkZXIiLCAiMXN0IE9yZGVyIiwgIjJuZCBPcmRlciIsICIzcmQgT3JkZXIiKSkgKw0KIyAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IHhfbWluLA0KIyAgICAgICAgICAgIHkgPSB5X21heCwNCiMgICAgICAgICAgICBsYWJlbCA9IHBhc3RlMCgiQmVzdCBmaXQ6ICIscG9seV9pX25hbWUsICJcbiIsICJyMiA9ICIsIHJfaSksDQojICAgICAgICAgICAgaGp1c3QgPSAwLCB2anVzdCA9IDEsIHNpemUgPSA0KSArDQojICAgZmFjZXRfd3JhcCh+bW9kZWxfdHlwZSkgKw0KIyAgIGxhYnMoDQojICAgICB0aXRsZSA9IHBhc3RlKCJNb2RlbCBGaXRzIHZzIFJhdyBEYXRhIGZvciBJRCIsIGlkX2kpLA0KIyAgICAgeCA9ICJEaXNzb2x2ZWQgb3h5Z2VuIHBlcmNlbnRhZ2UgKERPKSIsDQojICAgICB5ID0gIk1PMiAobWcgbzIvZy9oKSIsDQojICAgICBjb2xvdXIgPSAiTW9kZWwiKSArDQojICAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KYGBge3J9DQojIGdsb2JhbF9tb2RlbHMgPC0gbGlzdCgNCiMgICAgIGxtXzAgPSBsbWVyKE1PMl9nIH4gMSArICgxfGlkKSwgZGF0YSA9IHNsb3BlX3RpZHkgJT4lIA0KIyAgICAgICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKHBvbHkgPT0gMCksIHdlaWdodHMgPSB3ZWlnaHRfc21yKSwNCiMgICAgIGxtXzEgPSBsbWVyKE1PMl9nIH4gRE8gKyAoMXxpZCksIGRhdGEgPSBzbG9wZV90aWR5ICU+JSANCiMgICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihwb2x5ID09IDEpLCB3ZWlnaHRzID0gd2VpZ2h0X3NtciksDQojICAgICBsbV8yID0gbG1lcihNTzJfZyB+IHBvbHkoRE8sIDIpICsgKDF8aWQpLCBkYXRhID0gc2xvcGVfdGlkeSAlPiUgDQojICAgICAgICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIocG9seSA9PSAyKSwgd2VpZ2h0cyA9IHdlaWdodF9zbXIpLA0KIyAgICAgbG1fMyA9IGxtZXIoTU8yX2cgfiBwb2x5KERPLCAzKSArICgxfGlkKSwgZGF0YSA9IHNsb3BlX3RpZHkgJT4lIA0KIyAgICAgICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKHBvbHkgPT0gMyksIHdlaWdodHMgPSB3ZWlnaHRfc21yKQ0KIyAgICkNCiMgDQojIA0KIyBnbG9iYWxfcHJlZGljdGlvbnMgPC0gZGF0YS5mcmFtZShETyA9IHNlcShtaW4oc2xvcGVfdGlkeSRETyksIG1heChzbG9wZV90aWR5JERPKSwgbGVuZ3RoLm91dCA9IDEwMCkpDQojIA0KIyBmb3IgKG1vZGVsX25hbWUgaW4gbmFtZXMoZ2xvYmFsX21vZGVscykpIHsNCiMgICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KA0KIyAgICAgZ2xvYmFsX21vZGVsc1tbbW9kZWxfbmFtZV1dLCANCiMgICAgIG5ld2RhdGEgPSBnbG9iYWxfcHJlZGljdGlvbnMsIA0KIyAgICAgcmUuZm9ybSA9IE5BLCAgIyBFeGNsdWRlcyByYW5kb20gZWZmZWN0cyAocG9wdWxhdGlvbi1sZXZlbCBwcmVkaWN0aW9ucykNCiMgICAgIHNlLmZpdCA9IFRSVUUgICMgUmV0dXJucyBzdGFuZGFyZCBlcnJvcnMNCiMgICApDQojICAgDQojICAgZ2xvYmFsX3ByZWRpY3Rpb25zW1twYXN0ZTAobW9kZWxfbmFtZSwgIl9maXQiKV1dIDwtIHByZWRpY3Rpb25zJGZpdA0KIyAgIGdsb2JhbF9wcmVkaWN0aW9uc1tbcGFzdGUwKG1vZGVsX25hbWUsICJfbHdyIildXSA8LSBwcmVkaWN0aW9ucyRmaXQgLSAxLjk2ICogcHJlZGljdGlvbnMkc2UuZml0DQojICAgZ2xvYmFsX3ByZWRpY3Rpb25zW1twYXN0ZTAobW9kZWxfbmFtZSwgIl91cHIiKV1dIDwtIHByZWRpY3Rpb25zJGZpdCArIDEuOTYgKiBwcmVkaWN0aW9ucyRzZS5maXQNCiMgfQ0KIyANCiMgZ2xvYmFsX3ByZWRpY3Rpb25zX2xvbmcgPC0gZ2xvYmFsX3ByZWRpY3Rpb25zICU+JQ0KIyAgIHBpdm90X2xvbmdlcigNCiMgICAgIGNvbHMgPSBtYXRjaGVzKCJsbV8uKl9maXR8bG1fLipfbHdyfGxtXy4qX3VwciIpLA0KIyAgICAgbmFtZXNfdG8gPSBjKCJtb2RlbCIsICIudmFsdWUiKSwNCiMgICAgIG5hbWVzX3BhdHRlcm4gPSAiKGxtX1xcZCspXyguKikiDQojICAgKSAlPiUNCiMgICBkcGx5cjo6bXV0YXRlKGJlc3RfbW9kZWxfbmFtZSA9IGNhc2Vfd2hlbigNCiMgICAgIG1vZGVsID09ICJsbV8wIiB+ICIwdGgtb3JkZXIgcG9seW5vbWlhbCIsDQojICAgICBtb2RlbCA9PSAibG1fMSIgfiAiMXN0LW9yZGVyIHBvbHlub21pYWwiLA0KIyAgICAgbW9kZWwgPT0gImxtXzIiIH4gIjJuZC1vcmRlciBwb2x5bm9taWFsIiwNCiMgICAgIG1vZGVsID09ICJsbV8zIiB+ICIzcmQtb3JkZXIgcG9seW5vbWlhbCIsDQojICAgICBUUlVFIH4gIkVSUk9SIg0KIyAgICkpDQpgYGANCg0KRmlndXJlIDxicj4NCg0KYGBge3J9DQojIGJlc3Rfd2VpZ2h0ZWRfbW9kZWxfcHJlZCA8LSBiZXN0X3dlaWdodGVkX21vZGVsICU+JSANCiMgICBkcGx5cjo6bGVmdF9qb2luKC4sIG1vZGVsX3ByZWRzX2RmLCBieSA9IGMoImlkIiwgIm1vZGVsIikpICU+JSANCiMgICBkcGx5cjo6dW5ncm91cCgpICU+JSANCiMgICBkcGx5cjo6bXV0YXRlKGJlc3RfbW9kZWxfbmFtZSA9IGNhc2Vfd2hlbigNCiMgICAgICAgcG9seSA9PSAwIH4gIjB0aC1vcmRlciBwb2x5bm9taWFsIiwNCiMgICAgICAgcG9seSA9PSAxIH4gIjFzdC1vcmRlciBwb2x5bm9taWFsIiwNCiMgICAgICAgcG9seSA9PSAyIH4gIjJuZC1vcmRlciBwb2x5bm9taWFsIiwNCiMgICAgICAgcG9seSA9PSAzIH4gIjNyZC1vcmRlciBwb2x5bm9taWFsIiwNCiMgICAgICAgVFJVRSB+ICJFUlJPUiINCiMgICAgICkpDQojIA0KIyBhbm5vdGF0aW9uX2RhdGEgPC0gdGFibGVfYndtICU+JQ0KIyAgIGRwbHlyOjpzZWxlY3QoYmVzdF9tb2RlbF9uYW1lLCBuKSANCiMgDQojIGZpZ18xIDwtIGdncGxvdCgpICsNCiMgICBnZW9tX2xpbmUoZGF0YSA9IGJlc3Rfd2VpZ2h0ZWRfbW9kZWxfcHJlZCwgDQojICAgICAgICAgICAgIGFlcyh4ID0gRE8sIHkgPSBNTzJfcHJlZCwgY29sb3IgPSBpZCksIHNpemUgPSAxLCBhbHBoYSA9IDEpICsNCiMgICBnZW9tX3BvaW50KGRhdGEgPSBzbG9wZV90aWR5LCBhZXMoeCA9IERPLCB5ID0gTU8yX2cpLCBhbHBoYSA9IDAuMSwgY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDIpICsNCiMgICBnZW9tX3JpYmJvbihkYXRhID0gZ2xvYmFsX3ByZWRpY3Rpb25zX2xvbmcsIA0KIyAgICAgICAgICAgICAgIGFlcyh4ID0gRE8sIHltaW4gPSBsd3IsIHltYXggPSB1cHIsIGdyb3VwID0gbW9kZWwpLCANCiMgICAgICAgICAgICAgICBmaWxsID0gIiNGQzZDODUiLCBhbHBoYSA9IDAuMikgKyAgIyBTaGFkZWQgY29uZmlkZW5jZSBpbnRlcnZhbHMNCiMgICBnZW9tX2xpbmUoZGF0YSA9IGdsb2JhbF9wcmVkaWN0aW9uc19sb25nLCANCiMgICAgICAgICAgICAgYWVzKHggPSBETywgeSA9IGZpdCksIHNpemUgPSAxLjUsIGNvbG9yID0gIiNGRjAwN0YiKSArDQojICAgZmFjZXRfd3JhcCh+YmVzdF9tb2RlbF9uYW1lKSArDQojICAgc2NhbGVfY29sb3JfZ3JleShzdGFydCA9IDAuMSwgZW5kID0gMC45KSArDQojICAgbGFicygNCiMgICAgICAgdGl0bGUgPSBwYXN0ZSgiTW9kZWwgZXN0aW1hdGVzIGFuZCBvYnNlcnZlZCBkYXRhIGdyb3VwZWQgYnkgYmVzdCBmaXR0aW5nIG1vZGVsIiksDQojICAgICAgIHggPSAiRGlzc29sdmVkIG94eWdlbiBwZXJjZW50YWdlIChETykiLA0KIyAgICAgICB5ID0gIk1PMiAoTzIgbWcvZy9oKSIpICsNCiMgICB0aGVtZV9jbGFzc2ljKCkgKw0KIyAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KIyAgIGdlb21fdGV4dChkYXRhID0gYW5ub3RhdGlvbl9kYXRhLCANCiMgICAgICAgICAgICAgYWVzKHggPSAtSW5mLCB5ID0gSW5mLCBsYWJlbCA9IHBhc3RlMCgiaXRhbGljKG4pID09ICIsIG4pKSwgDQojICAgICAgICAgICAgIGhqdXN0ID0gLTAuMSwgdmp1c3QgPSAxLjIsIGluaGVyaXQuYWVzID0gRkFMU0UsIHBhcnNlID0gVFJVRSkNCiMgZmlnXzENCmBgYA0KDQojIyBQY3JpdCBDaGFib3QgbWV0aG9kDQoNCkZvciB0aG9zZSBmaXNoIHRoYXQgd2VyZSBiZXN0IG1vZGVsbGVkIHdpdGggYSAybmQgb3IgM3JkLW9yZGVyIHBvbHlub21pYWwgKCpuKiA9IDQ2KSB3ZSB3aWxsIGNoZWNrIHRvIHNlZSBpZiBhIFBjcml0IGlzIHByZXNlbnQuIFdlIGFyZSBmaWx0ZXJpbmcgdGhlIGRhdGEgZm9yIG9ubHkgdGhvc2UgZmlzaC4gPGJyPg0KDQoNCmBgYHtyfQ0KY2hlY2tfcGNyaXRfaWRzIDwtIGJheWVzX3JlZ19tb2RzX3ByZWRpY3Rpb25zICU+JSANCiAgZHBseXI6OmZpbHRlcihtb2RlbF90eXBlID09ICJsbV8xIikgJT4lIA0KICBkcGx5cjo6ZGlzdGluY3QoaWQpICU+JSANCiAgZHBseXI6OnB1bGwoaWQpDQoNCmNoZWNrX3Bjcml0X2RmIDwtIHNsb3BlX3RpZHkgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKGlkICVpbiUgY2hlY2tfcGNyaXRfaWRzKQ0KYGBgDQoNCg0KV2Ugd2lsbCBjYWxjdWxhdGUgUGNyaXQgdXNpbmcgQ2hhYm90IG1ldGhvZCBhbmQgZnVuY3Rpb24gY2FsY08yY3JpdC4gV2UgYXJlIHVzaW5nIG91ciBlc3RpbWF0ZXMgZm9yIFNNUiAobWVhbiBvZiBsb3dlc3QgdGhyZWUpLiA8YnI+DQoNClRoaXMgZnVuY3Rpb24gdXNlcyB0aGUgZmlmdGggcGVyY2VudGlsZSBvZiB0aGUgTU8yIHZhbHVlcyBvYnNlcnZlZCBhdCBkaXNzb2x2ZWQgb3h5Z2VuIGxldmVscyDiiaUgODAlIGFpciBzYXR1cmF0aW9uIGFzIHRoZSBjcml0ZXJpb24gdG8gYXNzZXNzIGxvdyAgTU8yIHZhbHVlcy4gVGhlIGFsZ29yaXRobSB0aGVuIGlkZW50aWZpZXMgYWxsIHRoZSBNTzIgbWVhc3VyZW1lbnRzIGdyZWF0ZXIgdGhhbiB0aGlzIG1pbmltYWxseSBhY2NlcHRhYmxlIE1PMiB2YWx1ZS4gV2l0aGluIHRoaXMgc3ViLXNldCwgaXQgaWRlbnRpZmllcyB0aGUgzIcgTU8yIG1lYXN1cmVtZW50IG1hZGUgYXQgdGhlIGxvd2VzdCBETyBhbmQgdGhlcmVhZnRlciBjb25zaWRlcnMgdGhpcyBETyBhcyBjYW5kaWRhdGUgZm9yIGJyZWFrcG9pbnQgKG5hbWVkIHBpdm90RE8gaW4gdGhlIHNjcmlwdCkuIEEgcmVncmVzc2lvbiBpcyB0aGVuIGNhbGN1bGF0ZWQgdXNpbmcgb2JzZXJ2YXRpb25zIGF0IERPIGxldmVscyA8IHBpdm90RE8sIGFuZCBhIGZpcnN0IGVzdGltYXRlIG9mIE8yY3JpdCBpcyBjYWxjdWxhdGVkIGFzIHRoZSBpbnRlcnNlY3Rpb24gb2YgdGhpcyByZWdyZXNzaW9uIGxpbmUgd2l0aCB0aGUgaG9yaXpvbnRhbCBsaW5lIHJlcHJlc2VudGluZyBTTVIuIFRoZSBzY3JpcHQgdGhlbiBnb2VzIHRocm91Z2ggdmFsaWRhdGlvbiBzdGVwcyB0byBlbnN1cmUgdGhhdCB0aGUgc2xvcGUgb2YgdGhlIHJlZ3Jlc3Npb24gaXMgbm90IHNvIGxvdyB0aGF0IHRoZSBsaW5lLCBwcm9qZWN0ZWQgdG8gbm9ybW94aWMgRE8gbGV2ZWxzLCBwYXNzZXMgYmVsb3cgYW55IE1PMiB2YWx1ZXMgb2JzZXJ2ZWQgaW4gbm9ybW94aWEuIEl0IGFsc28gZW5zdXJlcyB0aGF0IHRoZSBpbnRlcmNlcHQgaXMgbm90IGdyZWF0ZXIgdGhhbiB6ZXJvLiBDb3JyZWN0aXZlIG1lYXN1cmVzIGFyZSB0YWtlbiBpZiBzdWNoIHByb2JsZW1zIGFyZSBlbmNvdW50ZXJlZC48YnI+DQoNCmxvd2VzdE1PMiBkZWZhdWx0IGlzIHRoZSBxdWFudGlsZShEYXRhJE1PMltEYXRhJERPID49IDgwXSwgcD0wLjA1KS4gSXQgaXMgdXNlZCB0byBzZWdtZW50IHRoZSBkYXRhIGFuZCBsb2NhdGUgdGhlIHBpdm90RE8uIDxicj4NCg0KYGBge3J9DQppZHMgPC0gY2hlY2tfcGNyaXRfZGYgJT4lIA0KICBkcGx5cjo6ZGlzdGluY3QoaWQpICU+JSANCiAgZHBseXI6OnB1bGwoKQ0KDQpwY3JpdF9tb2RlbF9kZl9saXN0IDwtIGxpc3QoKQ0KcGNyaXRfbW9kZWxzIDwtICBsaXN0KCkNCg0KZm9yIChpZF9pIGluIGlkcykgew0KDQpkZl9pIDwtIGNoZWNrX3Bjcml0X2RmICU+JSANCiAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKQ0KDQpvMmNyaXQgPC0gY2FsY08yY3JpdChEYXRhID0gZGZfaSwgU01SID0gZGZfaSRTTVJbMV0sIGxvd2VzdE1PMj1OQSwgZ2FwTGltaXQgPSA0LA0KICAgICAgICAgICAgICAgICAgICAgbWF4Lm5iLk1PMi5mb3IucmVnID0gNykNCg0KdmF1bGUgPC0gbzJjcml0JG8yY3JpdA0KbG93ZXN0TU8yID0gcXVhbnRpbGUoZGZfaSRNTzJbZGZfaSRETyA+PSA4MF0sIHA9MC4wNSkNClNNUiA8LSBvMmNyaXQkU01SDQpuYl9tbzJfY29uZm9ybWluZyA8LSBvMmNyaXQkTmJfTU8yX2NvbmZvcm1pbmcNCnIyIDwtIG8yY3JpdCRyMg0KbWV0aG9kIDwtIG8yY3JpdCRNZXRob2QNCnAgPC0gbzJjcml0JFBbMV0NCg0KcGNyaXRfbW9kZWxfZGYgPC0gdGliYmxlKA0KICAgICAgaWQgPSBpZF9pLA0KICAgICAgcGNyaXRfdmF1bGUgPSB2YXVsZSwNCiAgICAgIHBjcml0X3NtciA9IFNNUiwNCiAgICAgIHBjcml0X2xvd2VzdE1PMiA9IGxvd2VzdE1PMiwNCiAgICAgIHBjcml0X25iX21vMl9jb25mb3JtaW5nID0gbmJfbW8yX2NvbmZvcm1pbmcsDQogICAgICBwY3JpdF9yMiA9IHIyLA0KICAgICAgcGNyaXRfbWV0aG9kID0gbWV0aG9kLA0KICAgICAgcGNyaXRfcCA9IHANCiAgICApDQoNCnBjcml0X21vZGVsX2RmX2xpc3RbW2lkX2ldXSA8LSBwY3JpdF9tb2RlbF9kZg0KDQpwY3JpdF9tb2RlbHNbW2lkX2ldXSA8LSBvMmNyaXQNCg0KfQ0KDQpwY3JpdF9tb2RlbF9kZiA8LSBiaW5kX3Jvd3MocGNyaXRfbW9kZWxfZGZfbGlzdCkNCmBgYA0KDQojIyMgUGxvdGluZyBQY3JpdA0KDQpIZXJlJ3MgdGhlIHBsb3RzIGZvciB0aGUgUGNyaXQgZXN0aW1hdGVzIDxicj4NCg0KYGBge3J9DQojIENyZWF0ZSBvdXRwdXQgZGlyZWN0b3J5IGlmIG5lZWRlZA0Kb3V0cHV0X2ZpZ19wY3JpdF9jaGFib3Rfd2QgPC0gZmlsZS5wYXRoKG91dHB1dF9maWdfd2QsICJtb2RlbF9jaGFib3QiKQ0KaWYgKCFkaXIuZXhpc3RzKG91dHB1dF9maWdfcGNyaXRfY2hhYm90X3dkKSkgew0KICBkaXIuY3JlYXRlKG91dHB1dF9maWdfcGNyaXRfY2hhYm90X3dkKQ0KfQ0KDQppZHMgPC0gY2hlY2tfcGNyaXRfZGYgJT4lIA0KICBkcGx5cjo6ZGlzdGluY3QoaWQpICU+JSANCiAgZHBseXI6OnB1bGwoKQ0KDQpwY3JpdF9jaGFib3RfbGlzdCA8LSBsaXN0KCkNCg0KIyBPcGVuIGEgc2luZ2xlIFBERiBkZXZpY2UNCnBkZihmaWxlID0gZmlsZS5wYXRoKG91dHB1dF9maWdfcGNyaXRfY2hhYm90X3dkLCAiY29tYmluZWRfY2hhYm90X3Bsb3RzLnBkZiIpLCANCiAgICB3aWR0aCA9IDgsIGhlaWdodCA9IDYpDQoNCmZvciAoaWRfaSBpbiBpZHMpIHsNCiAgDQogIHIyIDwtIHBjcml0X21vZGVsX2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X3IyID0gcm91bmQocGNyaXRfcjIsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfcjIpDQogIA0KICBjb25mb3JtaW5nIDwtIHBjcml0X21vZGVsX2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X25iX21vMl9jb25mb3JtaW5nID0gcm91bmQocGNyaXRfbmJfbW8yX2NvbmZvcm1pbmcsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfbmJfbW8yX2NvbmZvcm1pbmcpDQogIA0KICBQIDwtIHBjcml0X21vZGVsX2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X3AgPSByb3VuZChwY3JpdF9wLCAzKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHBjcml0X3ApDQogIA0KICBTTVIgPC0gcGNyaXRfbW9kZWxfZGYgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocGNyaXRfc21yID0gcm91bmQocGNyaXRfc21yLCAzKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHBjcml0X3NtcikNCiAgDQogIGxvd2VzdE1PMiA8LSBwY3JpdF9tb2RlbF9kZiAlPiUgDQogICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKSAlPiUgDQogICAgZHBseXI6Om11dGF0ZShwY3JpdF9sb3dlc3RNTzIgPSByb3VuZChwY3JpdF9sb3dlc3RNTzIsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfbG93ZXN0TU8yKQ0KICANCiAgIyBHZW5lcmF0ZSBhbmQgcmVuZGVyIHRoZSBwbG90DQogIHBsb3RPMmNyaXQobzJjcml0b2JqID0gcGNyaXRfbW9kZWxzW1tpZF9pXV0pDQogIA0KICAjIEFkZCBhIHRpdGxlDQogIG10ZXh0KA0KICAgIHRleHQgPSBwYXN0ZTAoaWRfaSksDQogICAgc2lkZSA9IDMsIGxpbmUgPSAyLCBhZGogPSAwLA0KICAgIGNvbCA9ICJibHVlIiwgZm9udCA9IDIsIGNleCA9IDEuMg0KICApDQogIA0KICBtdGV4dCgNCiAgICB0ZXh0ID0gcGFzdGUwKCJSMiA9ICIsIHIyLCAiOyBwID0gIiwgUCwgIjsgQ1AgPCBTTVIgPSAiLCBjb25mb3JtaW5nLCAiOyBTTVIgPSAiLCBTTVIsICI7IGxvd2VzdE1PMiA9ICIsbG93ZXN0TU8yKSwNCiAgICBzaWRlID0gMywgbGluZSA9IDEsIGFkaiA9IDAsDQogICAgY29sID0gImJsdWUiLCBmb250ID0gMSwgY2V4ID0gMC44DQogICkNCn0NCg0KIyBDbG9zZSB0aGUgUERGIGRldmljZSAqYWZ0ZXIqIHRoZSBsb29wDQpkZXYub2ZmKCkNCmBgYA0KDQpQcmludGluZyBpbiBodGxtIGRvY3VtZW50IDxicj4NCg0KYGBge3J9DQppZHMgPC0gY2hlY2tfcGNyaXRfZGYgJT4lIA0KICBkcGx5cjo6ZGlzdGluY3QoaWQpICU+JSANCiAgZHBseXI6OnB1bGwoKQ0KDQpmb3IgKGlkX2kgaW4gaWRzKSB7DQogIA0KICBjb21tZW50IDwtIGNoZWNrX3Bjcml0X2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6c2xpY2UoMSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUoY29tbWVudCA9IGlmX2Vsc2UoaXMubmEoY29tbWVudHMpLCAiIiwgcGFzdGUwKCIjIiwgY29tbWVudHMpKSkgJT4lIA0KICAgIHB1bGwoY29tbWVudCkNCiAgDQogIHIyIDwtIHBjcml0X21vZGVsX2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X3IyID0gcm91bmQocGNyaXRfcjIsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfcjIpDQogIA0KICBjb25mb3JtaW5nIDwtIHBjcml0X21vZGVsX2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X25iX21vMl9jb25mb3JtaW5nID0gcm91bmQocGNyaXRfbmJfbW8yX2NvbmZvcm1pbmcsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfbmJfbW8yX2NvbmZvcm1pbmcpDQogIA0KICBQIDwtIHBjcml0X21vZGVsX2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X3AgPSByb3VuZChwY3JpdF9wLCAzKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHBjcml0X3ApDQogIA0KICBTTVIgPC0gcGNyaXRfbW9kZWxfZGYgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocGNyaXRfc21yID0gcm91bmQocGNyaXRfc21yLCAzKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHBjcml0X3NtcikNCiAgDQogIGxvd2VzdE1PMiA8LSBwY3JpdF9tb2RlbF9kZiAlPiUgDQogICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKSAlPiUgDQogICAgZHBseXI6Om11dGF0ZShwY3JpdF9sb3dlc3RNTzIgPSByb3VuZChwY3JpdF9sb3dlc3RNTzIsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfbG93ZXN0TU8yKQ0KICANCiAgIyBHZW5lcmF0ZSBhbmQgcmVuZGVyIHRoZSBwbG90DQogIHBsb3RPMmNyaXQobzJjcml0b2JqID0gcGNyaXRfbW9kZWxzW1tpZF9pXV0pDQogIA0KICAjIEFkZCBhIHRpdGxlDQogIG10ZXh0KA0KICAgIHRleHQgPSBwYXN0ZTAoaWRfaSwgIiAiLCBjb21tZW50KSwNCiAgICBzaWRlID0gMywgbGluZSA9IDIsIGFkaiA9IDAsDQogICAgY29sID0gImJsdWUiLCBmb250ID0gMiwgY2V4ID0gMS4yDQogICkNCiAgDQogIG10ZXh0KA0KICAgIHRleHQgPSBwYXN0ZTAoIlIyID0gIiwgcjIsICI7IHAgPSAiLCBQLCAiOyBDUCA8IFNNUiA9ICIsIGNvbmZvcm1pbmcsICI7IFNNUiA9ICIsIFNNUiwgIjsgbG93ZXN0TU8yID0gIixsb3dlc3RNTzIpLA0KICAgIHNpZGUgPSAzLCBsaW5lID0gMSwgYWRqID0gMCwNCiAgICBjb2wgPSAiYmx1ZSIsIGZvbnQgPSAxLCBjZXggPSAwLjgNCiAgKQ0KfQ0KYGBgDQoNCiMjIyBQY3JpdCBydWxlcyANCg0KV2UgbmVlZCB0byBzZXQgc29tZSBydWxlcyBhcyB0byB3aGVuIHRoZSBQY3JpdCBlc3RpbWF0ZXMgYXJlIHJlbGlhYmxlLCBhcyBpdCBzZWVtcyBtYW55IG9mIG91ciBmaXNoIGRvIG5vdCByZWFjaCBhIFBjcml0LiA8YnI+DQoNCldlIGNhbiBmaWx0ZXIgZm9yIG9ubHkgY2FzZXMgd2VyZSBhdCB0aGUgbG93ZXN0IE8yIHZhbHVlIHRocmVlIGNvbnNlY3V0aXZlIE1PMiBtZWFzdXJlcyBmdWxsIGJlbG93IG91ciBTTVIgYW5kIGZpZnRoIHBlcmNlbnRpbGUgb2YgdGhlIE1PMiB2YWx1ZXMgb2JzZXJ2ZWQgYXQgZGlzc29sdmVkIE8yIGxldmVscyA+IDgwJS4gSW4gdGhlIG1vZGVsIG91dHB1dCB0aGVzZSBhcmUgY2FsbGVkIG5iX21vMl9jb25mb3JtaW5nIHBvaW50cy4gV2UgY2FuIHRoZSB2aXN1YWxseSBpbnNwZWN0IHRoZXNlIHRvIHNlZSBpZiBhIFBjcml0IGlzIHByZXNlbnQuIA0KDQpgYGB7cn0NCnBjcml0X2xpc3QgPC0gcGNyaXRfbW9kZWxfZGYgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKHBjcml0X25iX21vMl9jb25mb3JtaW5nID4gMikgJT4lIA0KICBwdWxsKGlkKQ0KDQpwYXN0ZTAoIkJhc2VkIG9uIHRoaXMgcnVsZSB0aGVyZSBhcmUgIiwgbGVuZ3RoKHBjcml0X2xpc3QpLCAiIGZpc2ggd2l0aCBwb3NzaWJsZSBQY3JpdHMuIikNCmBgYA0KDQpIZXJlIGFyZSB0aGUgcGxvdHMgb2YgdGhlc2UgMTMgZmlzaCBmb3IgdmlzdWFsIGNvbmZpcm1hdGlvbiA8YnI+DQoNCmBgYHtyfQ0KZm9yIChpZF9pIGluIHBjcml0X2xpc3QpIHsNCiAgDQogIGNvbW1lbnQgPC0gY2hlY2tfcGNyaXRfZGYgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjpzbGljZSgxKSAlPiUgDQogICAgZHBseXI6Om11dGF0ZShjb21tZW50ID0gaWZfZWxzZShpcy5uYShjb21tZW50cyksICIiLCBwYXN0ZTAoIiMiLCBjb21tZW50cykpKSAlPiUgDQogICAgcHVsbChjb21tZW50KQ0KICANCiAgcjIgPC0gcGNyaXRfbW9kZWxfZGYgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocGNyaXRfcjIgPSByb3VuZChwY3JpdF9yMiwgMykpICU+JSANCiAgICBkcGx5cjo6cHVsbChwY3JpdF9yMikNCiAgDQogIGNvbmZvcm1pbmcgPC0gcGNyaXRfbW9kZWxfZGYgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocGNyaXRfbmJfbW8yX2NvbmZvcm1pbmcgPSByb3VuZChwY3JpdF9uYl9tbzJfY29uZm9ybWluZywgMykpICU+JSANCiAgICBkcGx5cjo6cHVsbChwY3JpdF9uYl9tbzJfY29uZm9ybWluZykNCiAgDQogIFAgPC0gcGNyaXRfbW9kZWxfZGYgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocGNyaXRfcCA9IHJvdW5kKHBjcml0X3AsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfcCkNCiAgDQogIFNNUiA8LSBwY3JpdF9tb2RlbF9kZiAlPiUgDQogICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKSAlPiUgDQogICAgZHBseXI6Om11dGF0ZShwY3JpdF9zbXIgPSByb3VuZChwY3JpdF9zbXIsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfc21yKQ0KICANCiAgbG93ZXN0TU8yIDwtIHBjcml0X21vZGVsX2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X2xvd2VzdE1PMiA9IHJvdW5kKHBjcml0X2xvd2VzdE1PMiwgMykpICU+JSANCiAgICBkcGx5cjo6cHVsbChwY3JpdF9sb3dlc3RNTzIpDQogIA0KICAjIEdlbmVyYXRlIGFuZCByZW5kZXIgdGhlIHBsb3QNCiAgcGxvdE8yY3JpdChvMmNyaXRvYmogPSBwY3JpdF9tb2RlbHNbW2lkX2ldXSkNCiAgDQogICMgQWRkIGEgdGl0bGUNCiAgbXRleHQoDQogICAgdGV4dCA9IHBhc3RlMChpZF9pLCAiICIsIGNvbW1lbnQpLA0KICAgIHNpZGUgPSAzLCBsaW5lID0gMiwgYWRqID0gMCwNCiAgICBjb2wgPSAiYmx1ZSIsIGZvbnQgPSAyLCBjZXggPSAxLjINCiAgKQ0KICANCiAgbXRleHQoDQogICAgdGV4dCA9IHBhc3RlMCgiUjIgPSAiLCByMiwgIjsgcCA9ICIsIFAsICI7IENQIDwgU01SID0gIiwgY29uZm9ybWluZywgIjsgU01SID0gIiwgU01SLCAiOyBsb3dlc3RNTzIgPSAiLGxvd2VzdE1PMiksDQogICAgc2lkZSA9IDMsIGxpbmUgPSAxLCBhZGogPSAwLA0KICAgIGNvbCA9ICJibHVlIiwgZm9udCA9IDEsIGNleCA9IDAuOA0KICApDQp9DQpgYGANCg0KIyMjIFBjcml0cyBudW1iZXJzDQoNCkJhc2VkIG9uIHZpc3VhbCBjaGVja3MgdGhlIGZvbGxvd2luZyBmaXNoIGRvIGhhdmUgY2xlYXIgUGNyaXQgdmFsdWVzDQpgYGB7cn0NCmRvX2hhdmVfcGNyaXQgPC0gYygiYV85XzIxbm92XzMiLCAiYl8wXzI0bm92XzEiLCAiYl8wXzI0bm92XzIiLCAiYl8wXzI1bm92XzEiLCAiYl8wXzI1bm92XzMiLCAiYl8wXzI2X25vdl8xIiwgImJfOV8yMV9ub3ZfMSIsICJiXzlfMjFub3ZfMiIsICJiXzlfMjFub3ZfMyIsICJkXzBfMjFub3ZfMyIpDQoNCm5fcGNyaXQgPC0gbGVuZ3RoKGRvX2hhdmVfcGNyaXQpDQoNCmhhdmVfcGNpcnQgPC0gcGNyaXRfbW9kZWxfZGYgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKGlkICVpbiUgZG9faGF2ZV9wY3JpdCkNCg0KbWVhbl9wY3JpdCA8LSBoYXZlX3BjaXJ0ICU+JSANCiAgZHBseXI6OnJlZnJhbWUobWVhbiA9IG1lYW4ocGNyaXRfdmF1bGUpKSAlPiUgDQogIHB1bGwobWVhbikgJT4lIA0KICByb3VuZCguLCAyKQ0KDQptaW5fcGNyaXQgPC0gaGF2ZV9wY2lydCAlPiUgDQogIGRwbHlyOjpyZWZyYW1lKG1pbiA9IG1pbihwY3JpdF92YXVsZSkpICU+JSANCiAgcHVsbChtaW4pICU+JSANCiAgcm91bmQoLiwgMikNCg0KbWF4X3Bjcml0IDwtIGhhdmVfcGNpcnQgJT4lIA0KICBkcGx5cjo6cmVmcmFtZShtYXggPSBtYXgocGNyaXRfdmF1bGUpKSAlPiUgDQogIHB1bGwobWF4KSAlPiUgDQogIHJvdW5kKC4sIDIpDQoNCnByaW50KHBhc3RlMCgiVGhlcmUgYXJlICIsIG5fcGNyaXQsICIgZmlzaCB3aXRoIGlkZW50aWZpZWQgUGNyaXRzIGFuZCB0aGUgbWVhbiBQY3JpdCBpcyAiLCBtZWFuX3Bjcml0LCAiIChyYW5nZTogIiwgDQogICAgICAgICAgICAgbWluX3Bjcml0LCAi4oCTIiwgbWF4X3Bjcml0LCAiKSIpKQ0KYGBgDQoNCg0KIyBBZGRpdGlvbmFsIGNvZGUgdXNlZCB0byB2YWxpZGF0ZSBhcHByb2FjaA0KDQojIyMgRnJlcXVlbnRpc3Qgd2VpZ2h0ZWQgcmVncmVzc2lvbnMNCg0KV2Ugd2lsbCB1c2Ugd2VpZ2h0ZWQgcmVncmVzc2lvbiB0byBhY2NvdW50IGZvciBhIGhpZ2hlciBkZW5zaXR5IG9mIGRhdGEgYXQgbm9ybW94aWMgY29uZGl0aW9ucyAoaS5lLiBTTVIgdmFsdWVzKS4gSSBoYXZlIHVzZWQgdHdvIGRpZmZlcmVudCB3ZWlnaHRpbmcgYXBwcm9hY2hlcywgKDEpIHdlaWdodGluZyB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIGRhdGEgcG9pbnQgYmFzZWQgb24gdGhlIGZyZXF1ZW5jeSBvZiBwb2ludHMgaW4gYSBnaXZlbiBvMiBzcGFjZSAoMTIgZXZlbmx5IHNwYWNlZCBiaW5zKSwgb3IgKDIpIHdlaWdodGluZyBvbmx5IHRoZSBTTVIgc2xvcGVzLCBhcyB0aGV5IGFyZSB0aGUgb25seSB2YWx1ZXMgdGhhdCBoYXZlIHJlcGVhdGVkIG1lYXN1cmVzLiBQb2ludHMgdGhhdCBoYXZlIGhpZ2hlciB3ZWlnaHRzIGluZmx1ZW5jZSB0aGUgbW9kZWwgZml0IG1vcmUsIHdoaWxlIHBvaW50cyB3aXRoIGxvd2VyIHdlaWdodHMgaGF2ZSBsZXNzIGltcGFjdC4gQSBoaWdoIGRlbnNpdHkgb2YgcG9pbnRzIGF0IGhpZ2ggbzIgdmFsdWVzIGNvdWxkIGxlYWQgdG8gb3ZlcmZpdHRpbmcgaW4gdGhhdCByZWdpb24sIHdoaWxlIHVuZGVyZml0dGluZyBvciBtaXNyZXByZXNlbnRpbmcgdHJlbmRzIGluIGxvd2VyLWRlbnNpdHkgcmVnaW9ucyAoZS5nLiwgbG93IG8yIHZhdWxlcykuIDxicj4NCg0KSGVyZSB3ZSBhcmUgbWFraW5nIHRoZSB0d28gd2VpZ2h0aW5ncy4gVGhlIGZpcnN0IGlzIGFjaGlldmVkIGJ5IHNwaXRpbmcgdGhlIG8yIGRhdGEgaW4gMTIgZXZlbmx5IHNwYWNlZCBiaW5zLCBhbmQgc3VtbWluZyB0aGUgbnVtYmVyIG9mIGRhdGEgcG9pbnRzIGluIHRoYXQgYmluLCB0aGUgd2VpZ2h0IGlzIHRoZW4gZ2l2ZW4gdG8gZGF0YSBwb2ludHMgd2l0aGluIHRoYXQgYmluIGJhc2VkIG9uIHRoZSBpbnZlcnNlIGZyZXF1ZW5jeSBvZiBkYXRhIHBvaW50cy5UaGUgc2Vjb25kIG1ldGhvZHMsIGlzIHNpbXBseSBhcHBsaWVkIHRvIHRoZSBTTVIgbWVhc3VyZW1lbnRzLCBhbmQgaXMgdGhlIGludmVyc2UgZnJlcXVlbmN5IG9mIFNNUiBtZWFzdXJlbWVudHMuIDxicj4NCg0KYGBge3J9DQpzbG9wZV90aWR5IDwtIHNsb3BlX3RpZHkgJT4lDQogIGRwbHlyOjpncm91cF9ieShpZCkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKG8yX2JpbiA9IGN1dChETywgYnJlYWtzID0gMTIpLA0KICAgICAgICAgICAgICAgIHdlaWdodF9zbXIgPSBpZl9lbHNlKHBoYXNlID09ICJzbXIiLCAxL3N1bShwaGFzZSA9PSAic21yIiksIDEpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUNCiAgZHBseXI6Omdyb3VwX2J5KGlkLCBvMl9iaW4pICU+JSANCiAgZHBseXI6Om11dGF0ZSgNCiAgICBiaW5fZnJlcSA9IGxlbmd0aChvcmRlciksICAgICAgICAgICMgQ291bnQgcG9pbnRzIGluIGVhY2ggYmluDQogICAgd2VpZ2h0X2JpbnMgPSAxIC8gYmluX2ZyZXEgICAgICAgICAgICMgV2VpZ2h0ID0gaW52ZXJzZSBmcmVxdWVuY3kNCiAgICAgICkgJT4lIA0KICB1bmdyb3VwKCkNCmBgYA0KDQpIZXJlIHdlIGFyZSBidWlsZGluZyB0aGUgd2VpZ2h0ZWQgcmVncmVzc2lvbnMuIDxicj4NCg0KYGBge3J9DQppZHMgPC0gc2xvcGVfdGlkeSAlPiUNCiAgZHBseXI6OmRpc3RpbmN0KGlkKSAlPiUNCiAgcHVsbChpZCkgJT4lDQogIGFzLmxpc3QoKQ0KDQp3ZWlnaHRlZF9tb2RlbF9jb21wYXJpc29uX2xpc3QgPC0gbGlzdCgpDQoNCndlaWdodGVkX21vZGVsX3Jlc3VsdHNfbGlzdCA8LSBsaXN0KCkNCg0KZm9yIChpZF9pIGluIGlkcykgew0KICANCiAgIyBGaWx0ZXIgZGF0YSBmb3IgdGhlIGN1cnJlbnQgSUQNCiAgZGZfaSA8LSBzbG9wZV90aWR5ICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkNCiAgDQogIA0KICAjIEZpdCBtb2RlbHMgd2l0aCB3ZWlnaHRzDQogIG1vZGVscyA8LSBsaXN0KA0KICAgIGxtXzAgPSBsbShNTzJfZyB+IDEsIGRhdGEgPSBkZl9pLCB3ZWlnaHRzID0gd2VpZ2h0X3NtciksICAgICAgICAgICAgICAgICMgMHRoLW9yZGVyIChjb25zdGFudCBtZWFuKQ0KICAgIGxtXzEgPSBsbShNTzJfZyB+IERPLCBkYXRhID0gZGZfaSwgd2VpZ2h0cyA9IHdlaWdodF9zbXIpLCAgICAgICAgICAgICAgICMgMXN0LW9yZGVyIChsaW5lYXIpDQogICAgbG1fMiA9IGxtKE1PMl9nIH4gcG9seShETywgMiksIGRhdGEgPSBkZl9pLCB3ZWlnaHRzID0gd2VpZ2h0X3NtciksICAgICAgIyAybmQtb3JkZXIgKHF1YWRyYXRpYykNCiAgICBsbV8zID0gbG0oTU8yX2cgfiBwb2x5KERPLCAzKSwgZGF0YSA9IGRmX2ksIHdlaWdodHMgPSB3ZWlnaHRfc21yKSAgICAgICAjIDNyZC1vcmRlciAoY3ViaWMpDQogICkNCiAgDQogICMgRXh0cmFjdCBtZXRyaWNzIHRvIGNvbXBhcmUgbW9kZWxzDQogIHdlaWdodGVkX21vZGVsX2NvbXBhcmlzb25fbGlzdFtbaWRfaV1dIDwtIHB1cnJyOjptYXBfZGYobW9kZWxzLCBnbGFuY2UsIC5pZCA9ICJtb2RlbCIpICU+JQ0KICAgIGRwbHlyOjptdXRhdGUoaWQgPSBpZF9pKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KGlkLCBldmVyeXRoaW5nKCkpDQogIA0KICB3ZWlnaHRlZF9tb2RlbF9yZXN1bHRzX2xpc3RbW2lkX2ldXSA8LSBtYXBfZGYobW9kZWxzLCB+IHRpZHkoLngsIGNvbmYuaW50ID0gVFJVRSksIC5pZCA9ICJtb2RlbCIpICU+JQ0KICAgIGNsZWFuX25hbWVzKCkgJT4lIA0KICAgIG11dGF0ZShpZCA9IGlkX2kpICU+JQ0KICAgIHNlbGVjdChpZCwgZXZlcnl0aGluZygpKQ0KfQ0KDQojIENvbWJpbmUgZGF0YWZyYW1lcyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUNCndlaWdodGVkX21vZGVsX2NvbXBhcmlzb24gPC0gYmluZF9yb3dzKHdlaWdodGVkX21vZGVsX2NvbXBhcmlzb25fbGlzdCkgJT4lDQogIGRwbHlyOjptdXRhdGUocG9seSA9IGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3JlbW92ZV9hbGwobW9kZWwsICJsbV8iKSkpDQoNCndlaWdodGVkX21vZGVsX3Jlc3VsdHMgPC0gYmluZF9yb3dzKHdlaWdodGVkX21vZGVsX3Jlc3VsdHNfbGlzdCkgJT4lDQogIGRwbHlyOjptdXRhdGUocG9seSA9IGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3JlbW92ZV9hbGwobW9kZWwsICJsbV8iKSkpDQpgYGANCg0KU2VsZWN0aW5nIHRoZSBiZXN0IGZpdHRpbmcgbW9kZWwgPGJyPg0KDQpgYGB7cn0NCmJlc3Rfd2VpZ2h0ZWRfbW9kZWwgPC0gd2VpZ2h0ZWRfbW9kZWxfY29tcGFyaXNvbiAlPiUgDQogIGRwbHlyOjpncm91cF9ieShpZCkgJT4lDQogIGRwbHlyOjptdXRhdGUoDQogICAgQUlDX3JhbmsgPSByYW5rKC1BSUMpLCAgIyBSYW5rIGRlc2NlbmRpbmcgZm9yIEFJQw0KICAgIEJJQ19yYW5rID0gcmFuaygtQklDKSwgICMgUmFuayBkZXNjZW5kaW5nIGZvciBCSUMNCiAgICBsb2dMaWtfcmFuayA9IHJhbmsobG9nTGlrKSwgICMgUmFuayBhc2NlbmRpbmcgZm9yIGxvZ0xpaw0KICAgIHNjb3JlID0gQUlDX3JhbmsgKyBCSUNfcmFuayArIGxvZ0xpa19yYW5rICAjIENvbWJpbmVkIHNjb3JlDQogICkgJT4lDQogIGRwbHlyOjphcnJhbmdlKGRlc2Moc2NvcmUpKSAlPiUgICMgQXJyYW5nZSBieSBkZXNjZW5kaW5nIHNjb3JlDQogIGRwbHlyOjpzbGljZSgxKQ0KYGBgDQoNCk5vdyB3ZSBhcmUgcGxvdHRpbmcgZWFjaCBvZiB0aGUgcmVncmVzc2lvbnMuIEZpcnN0IG1ha2luZyBhIGRpcmVjdG9yeSB0byBzYXZlIHRoZSBmaWd1cmVzIDxicj4NCg0KYGBge3J9DQppbmNyZW1lbnRhbF9yZWdfZnJlcV93ZCA8LSBmaWxlLnBhdGgob3V0cHV0X2ZpZ193ZCwgImluY3JlbWVudGFsX3JlZ3Jlc3Npb25zLi9mcmVxIikNCmlmICghZGlyLmV4aXN0cyhpbmNyZW1lbnRhbF9yZWdfZnJlcV93ZCkpIHsNCiAgZGlyLmNyZWF0ZShpbmNyZW1lbnRhbF9yZWdfZnJlcV93ZCkNCn0NCmBgYA0KDQpQbG90aW5nIGFsbCByZWdyZXNzaW9uLCBhbmQgaGlnaGxpZ2h0aW5nIHRoZSBtb2RlbCB0aGF0IGhhcyB0aGUgYmVzdCBmaXQsIGJhc2VkIG9uIEFJQyB2YWx1ZXMgPGJyPg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgbGlzdCB0byBzdG9yZSB0aGUgcGxvdHMNCnBsb3RzIDwtIGxpc3QoKQ0KbW9kZWxfcHJlZHNfbGlzdCA8LSBsaXN0KCkNCg0KZm9yIChpZF9pIGluIGlkcykgew0KICANCiAgIyBGaWx0ZXIgZGF0YSBmb3IgdGhlIGN1cnJlbnQgSUQNCiAgZGZfaSA8LSBzbG9wZV90aWR5ICU+JQ0KICAgIGZpbHRlcihpZCA9PSBpZF9pKQ0KICANCiAgeF9taW4gPC0gZGZfaSAlPiUNCiAgICBkcGx5cjo6cmVmcmFtZShtaW4gPSBtaW4oRE8pLCBuYS5ybSA9IFRSVUUpICU+JSANCiAgICBkcGx5cjo6cHVsbChtaW4pDQogIA0KICB5X21heCA8LSBkZl9pICU+JQ0KICAgIGRwbHlyOjpyZWZyYW1lKG1heCA9IG1heChNTzJfZyksIG5hLnJtID0gVFJVRSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKG1heCkNCiAgDQogICMgR2V0IG1vZGVsIHByZWRpY3Rpb25zDQogIGRmX3ByZWRpY3Rpb25zIDwtIGRhdGEuZnJhbWUoRE8gPSBzZXEobWluKGRmX2kkRE8pLCBtYXgoZGZfaSRETyksIGxlbmd0aC5vdXQgPSAxMDApKQ0KICBkZl9wcmVkaWN0aW9ucyRpZCA8LSBpZF9pDQogIA0KDQogICMgR2VuZXJhdGUgcHJlZGljdGlvbnMgZm9yIGVhY2ggbW9kZWwNCiAgbW9kZWxzIDwtIGxpc3QoDQogICAgbG1fMCA9IGxtKE1PMl9nIH4gMSwgZGF0YSA9IGRmX2ksIHdlaWdodHMgPSBkZl9pJHdlaWdodF9zbXIpLA0KICAgIGxtXzEgPSBsbShNTzJfZyB+IERPLCBkYXRhID0gZGZfaSwgd2VpZ2h0cyA9IGRmX2kkd2VpZ2h0X3NtciksDQogICAgbG1fMiA9IGxtKE1PMl9nIH4gcG9seShETywgMiksIGRhdGEgPSBkZl9pLCB3ZWlnaHRzID0gZGZfaSR3ZWlnaHRfc21yKSwNCiAgICBsbV8zID0gbG0oTU8yX2cgfiBwb2x5KERPLCAzKSwgZGF0YSA9IGRmX2ksIHdlaWdodHMgPSBkZl9pJHdlaWdodF9zbXIpDQogICkNCiAgDQogIGJlc3Rfd2VpZ2h0ZWRfbW9kZWxfaSA8LSBiZXN0X3dlaWdodGVkX21vZGVsICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpDQogIA0KICBwb2x5X2lfbmFtZSA8LSBiZXN0X3dlaWdodGVkX21vZGVsX2kgJT4lDQogICAgZHBseXI6Om11dGF0ZShuYW1lID0gY2FzZV93aGVuKA0KICAgICAgcG9seSA9PSAwIH4gIjB0aC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIHBvbHkgPT0gMSB+ICIxc3Qtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgICBwb2x5ID09IDIgfiAiMm5kLW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgcG9seSA9PSAzIH4gIjNyZC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIFRSVUUgfiAiRVJST1IiDQogICAgKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKG5hbWUpDQogIA0KICByX2kgPC0gYmVzdF93ZWlnaHRlZF9tb2RlbF9pICU+JSANCiAgICBkcGx5cjo6cHVsbChyLnNxdWFyZWQpICU+JSANCiAgICByb3VuZCguLCAzKQ0KICANCiAgcF92YWx1ZV9pIDwtIGJlc3Rfd2VpZ2h0ZWRfbW9kZWxfaSAlPiUgDQogICAgZHBseXI6OnB1bGwocC52YWx1ZSkgJT4lIA0KICAgIHJvdW5kKC4sIDMpDQogIA0KICBwX3ZhbHVlX2kgPC0gaWZfZWxzZShwX3ZhbHVlX2kgPCAwLjAwMSwgIjwgMC4wMDEiLCBwYXN0ZTAoIj0gIiwgcF92YWx1ZV9pKSkNCiAgDQogIG1vZGVsX2kgPC0gYmVzdF93ZWlnaHRlZF9tb2RlbF9pICU+JSANCiAgICBkcGx5cjo6cHVsbChtb2RlbCkNCiAgDQogICMgQWRkIHByZWRpY3Rpb25zIHRvIHRoZSBkYXRhZnJhbWUNCiAgZm9yIChtb2RlbF9uYW1lIGluIG5hbWVzKG1vZGVscykpIHsNCiAgICBkZl9wcmVkaWN0aW9uc1tbbW9kZWxfbmFtZV1dIDwtIHByZWRpY3QobW9kZWxzW1ttb2RlbF9uYW1lXV0sIG5ld2RhdGEgPSBkZl9wcmVkaWN0aW9ucykNCiAgfQ0KICANCiAgIyBSZXNoYXBlIGRhdGEgZm9yIHBsb3R0aW5nDQogIGRmX3ByZWRpY3Rpb25zX2xvbmcgPC0gZGZfcHJlZGljdGlvbnMgJT4lDQogICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgibG1fIiksIG5hbWVzX3RvID0gIm1vZGVsIiwgdmFsdWVzX3RvID0gIk1PMl9wcmVkIikgJT4lDQogICAgbXV0YXRlKGxpbmVfc2l6ZSA9IGlmX2Vsc2UobW9kZWwgPT0gbW9kZWxfaSwgMiwgMSksDQogICAgICAgICAgIGFscGhhX3ZhbHVlID0gaWZfZWxzZShtb2RlbCA9PSBtb2RlbF9pLCAxLCAwLjQpKQ0KICANCiAgIyBDcmVhdGUgdGhlIHBsb3QNCiAgcCA8LSBnZ3Bsb3QoKSArDQogICAgZ2VvbV9wb2ludChkYXRhID0gZGZfaSwgYWVzKHggPSBETywgeSA9IE1PMl9nKSwgYWxwaGEgPSAwLjYsIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAyKSArDQogICAgZ2VvbV9saW5lKGRhdGEgPSBkZl9wcmVkaWN0aW9uc19sb25nLCANCiAgICAgICAgICAgICAgYWVzKHggPSBETywgeSA9IE1PMl9wcmVkLCBjb2xvdXIgPSBtb2RlbCwgc2l6ZSA9IGxpbmVfc2l6ZSwgYWxwaGEgPSBhbHBoYV92YWx1ZSkpICsNCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoInJlZCIsICJibHVlIiwgImdyZWVuIiwgInB1cnBsZSIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjB0aCBPcmRlciIsICIxc3QgT3JkZXIiLCAiMm5kIE9yZGVyIiwgIjNyZCBPcmRlciIpKSArDQogICAgc2NhbGVfc2l6ZV9pZGVudGl0eSgpICsgICMgVXNlIHRoZSBzaXplIHZhbHVlcyBkaXJlY3RseQ0KICAgIHNjYWxlX2FscGhhX2lkZW50aXR5KGd1aWRlID0gIm5vbmUiKSArICAjIFJlbW92ZSB0aGUgYWxwaGEgbGVnZW5kIA0KICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IHhfbWluLCANCiAgICAgICAgICAgICB5ID0geV9tYXgsIA0KICAgICAgICAgICAgIGxhYmVsID0gcGFzdGUwKCJCZXN0IGZpdDogIixwb2x5X2lfbmFtZSwgIlxuIiwgIlIgPSAiLCByX2ksICI7IFAgIiwgcF92YWx1ZV9pKSwgDQogICAgICAgICAgICAgaGp1c3QgPSAwLCB2anVzdCA9IDEsIHNpemUgPSA0KSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gcGFzdGUoIk1vZGVsIEZpdHMgdnMgUmF3IERhdGEgZm9yIElEIiwgaWRfaSksDQogICAgICB4ID0gIkRpc3NvbHZlZCBveHlnZW4gcGVyY2VudGFnZSAoRE8pIiwNCiAgICAgIHkgPSAiTU8yIChvMiBtZy9nL2gpIiwNCiAgICAgIGNvbG91ciA9ICJNb2RlbCIpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkNCiAgDQogICMgU3RvcmUgdGhlIHBsb3QNCiAgcGxvdHNbW2lkX2ldXSA8LSBwDQogIG1vZGVsX3ByZWRzX2xpc3RbW2lkX2ldXSA8LSBkZl9wcmVkaWN0aW9uc19sb25nDQogIA0KICBwcmludChwKQ0KfQ0KDQoNCiNUbyBzYXZlIGFsbCBwbG90cyB0byBpbmRpdmlkdWFsIGZpbGVzDQpmb3IgKGlkX2kgaW4gaWRzKSB7DQogIGdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlMChpbmNyZW1lbnRhbF9yZWdfZnJlcV93ZCwgIi4vcGxvdF8iLCBpZF9pLCAiLnBuZyIpLCBwbG90ID0gcGxvdHNbW2lkX2ldXSwgd2lkdGggPSA4LCBoZWlnaHQgPSA2KQ0KfQ0KDQptb2RlbF9wcmVkc19kZiA8LSBiaW5kX3Jvd3MobW9kZWxfcHJlZHNfbGlzdCkNCmBgYA0KDQoNClRoZSBiZXN0IGZpdHRpbmcgbW9kZWxzIHdlcmUgbW9zdCBvZnRlbiBhIDNyZC1vcmRlciBwb2x5bm9taWFsICgqbiogPSAyNywgNDYuNTUlKSBvciBhIDJuZC1vcmRlciBwb2x5bm9taWFsICgqbiogPSAxOSwgMzIuNzYlKS4gVGhpcyBjb3VsZCBzdWdnZXN0IHRoZSBwcmVzZW5jZSBvZiBhIGNyaXRpY2FsIG94eWdlbiB0aHJlc2hvbGQgKFBjcml0KSB3aGVyZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbzIgYW5kIE1PMiBjaGFuZ2VzLiBUbyBjb25maXJtIHRoZWlyIGlzIGEgUGNyaXQsIHdlIG5lZWQgdG8gdmFsaWRhdGVkIHRoZSBzaGFwZSBvZiB0aGUgcG9seW5vbWlhbHMgYW5kIGluIHNob3VsZCB1c2UgYSBtb3JlIHNwZWNpZmljIG1vZGVsIHRvIHRlc3QgdGhlIFBjcml0IHZhbHVlLiBUaGlzIHR5cGUgb2YgbW9kZWwgaXMgaW5kaWNhdGl2ZSBvZiAqKm94eXJlZ3VsYXRvcioqLiA8YnI+DQoNClRoZSBuZXh0IG1vc3QgY29tbW9uIGFyZSAwdGgtb3JkZXIgYW5kIDFzdC1vcmRlciBwb2x5bm9taWFscyAoYm90aCAqbiogPSA2LCAxMC4zNCUpLiBJbiB0aGUgY2FzZSBvZiB0aGUgMHRoLW9yZGVyIG1vZGVsLCBpdCBzdWdnZXN0cyB0aGF0IE1PMiBkb2VzIG5vdCBzaG93IGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBkZXBlbmRlbmNlIG9uIHRoZSBvMi4gSW4gb3RoZXIgd29yZHMsIHRoZSBtZXRhYm9saWMgcmF0ZSBkb2VzIG5vdCBhZGp1c3QgYmFzZWQgb24gb3h5Z2VuIGF2YWlsYWJpbGl0eSwgYW5kIHRoZXJlIGlzIG5vIGNsZWFyIGNyaXRpY2FsIG94eWdlbiB0aHJlc2hvbGQgKFBjcml0KSB3aGVyZSB0aGUgcmVsYXRpb25zaGlwIGNoYW5nZXMuIFRoaXMgaXMgaW5kaWNhdGl2ZSBvZiBhICoqb3h5cmVndWxhdG9yKiouIEluIHRoZSBjYXNlIG9mIHRoZSAxc3Qtb3JkZXIgcG9seW5vbWlhbHMsIGl0IHN1Z2dlc3QgdGhlIHByZXNlbmNlcyBvZiBsaW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gbzIgYW5kIE1PMiwgd2hpY2ggaXMgaW5kaWNhdGl2ZSBvZiAqKm94eWNvbmZvcm1lcioqLiBIb3dldmVyLCB0byBiZSB0cnVlIGV2aWRlbmNlIG9mIGEgb3h5Y29uZm9ybWVyIHRoaXMgcmVsYXRpb25zaGlwIHNob3VsZCBiZSBwb3NpdGl2ZSAoaS5lLiBhcyBvMiBmYWxscyBNTzIgYWxzbyBmYWxscykuIE9ubHkgMyBvZiB0aGUgNiBpbmRpdmlkdWFscyBiZXN0IG1vZGVsbGVkIHdpdGggYSBsaW5lYXIgZnVuY3Rpb24gd2VyZSBwb3NpdGl2ZSwgYW5kIG9ubHkgMiB3ZXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgKFRhYmxlIDEpLiA8YnI+DQoNCmBgYHtyfQ0KdG90YWxfZmlzaCA8LSBucm93KGJlc3Rfd2VpZ2h0ZWRfbW9kZWwpDQoNCnRhYmxlX2J3bSA8LSBiZXN0X3dlaWdodGVkX21vZGVsICU+JSANCiAgZHBseXI6Omdyb3VwX2J5KHBvbHkpICU+JSANCiAgZHBseXI6OnJlZnJhbWUobiA9IGxlbmd0aChpZCksDQogICAgICAgICAgICAgICAgIHBlcmNlbnQgPSByb3VuZCgobi90b3RhbF9maXNoKSoxMDAsMikpICU+JSANCiAgZHBseXI6Om11dGF0ZShiZXN0X21vZGVsX25hbWUgPSBjYXNlX3doZW4oDQogICAgICBwb2x5ID09IDAgfiAiMHRoLW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgcG9seSA9PSAxIH4gIjFzdC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIHBvbHkgPT0gMiB+ICIybmQtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgICBwb2x5ID09IDMgfiAiM3JkLW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgVFJVRSB+ICJFUlJPUiINCiAgICApKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QoYmVzdF9tb2RlbF9uYW1lLCBldmVyeXRoaW5nKCksIC1wb2x5KQ0KDQoNCnRhYmxlX2J3bSAlPiUgDQogICBndCgpICU+JSANCiAgY29sc19hbGlnbigNCiAgICBhbGlnbiA9ICJjZW50ZXIiLCANCiAgICBjb2x1bW5zID0gZXZlcnl0aGluZygpDQogICkNCmBgYA0KDQpBZGRpbmcgbW9kZWwgaW5mb3JtYXRpb24gdG8gdGhlIGZ1bGwgZGF0YSBmcmFtZSA8YnI+DQoNCmBgYHtyfQ0Kc2xvcGVfdGlkeSA8LSBmdWxsX2pvaW4oc2xvcGVfdGlkeSwgYmVzdF93ZWlnaHRlZF9tb2RlbCwgYnkgPSAiaWQiKSAlPiUgDQogIGRwbHlyOjpyZW5hbWUobW9kZWxfZnJlcSA9IG1vZGVsLA0KICAgICAgICAgICAgICAgIHJfc3F1YXJlZF9mcmVxID0gIHIuc3F1YXJlZCwNCiAgICAgICAgICAgICAgICBhZGpfcl9zcXVhcmVkX2ZyZXEgPSBhZGouci5zcXVhcmVkLA0KICAgICAgICAgICAgICAgIHNpZ21hX2ZyZXEgPSBzaWdtYSkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGJlc3RfbW9kZWxfbmFtZSA9IGNhc2Vfd2hlbigNCiAgICAgIHBvbHkgPT0gMCB+ICIwdGgtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgICBwb2x5ID09IDEgfiAiMXN0LW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgcG9seSA9PSAyIH4gIjJuZC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIHBvbHkgPT0gMyB+ICIzcmQtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgICBUUlVFIH4gIkVSUk9SIikpDQpgYGANCg0KSGVyZSB3ZSBhcmUgZ3JvdXBpbmcgZmlzaCBieSBiZXN0IGZpdHRpbmcgbW9kZWwgYW5kIGdldHRpbmcgYW4gYXZlcmFnZSB0cmVuZCAgDQoNCmBgYHtyfQ0KZ2xvYmFsX21vZGVscyA8LSBsaXN0KA0KICAgIGxtXzAgPSBsbWVyKE1PMl9nIH4gMSArICgxfGlkKSwgZGF0YSA9IHNsb3BlX3RpZHkgJT4lIA0KICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihwb2x5ID09IDApLCB3ZWlnaHRzID0gd2VpZ2h0X3NtciksDQogICAgbG1fMSA9IGxtZXIoTU8yX2cgfiBETyArICgxfGlkKSwgZGF0YSA9IHNsb3BlX3RpZHkgJT4lIA0KICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihwb2x5ID09IDEpLCB3ZWlnaHRzID0gd2VpZ2h0X3NtciksDQogICAgbG1fMiA9IGxtZXIoTU8yX2cgfiBwb2x5KERPLCAyKSArICgxfGlkKSwgZGF0YSA9IHNsb3BlX3RpZHkgJT4lIA0KICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihwb2x5ID09IDIpLCB3ZWlnaHRzID0gd2VpZ2h0X3NtciksDQogICAgbG1fMyA9IGxtZXIoTU8yX2cgfiBwb2x5KERPLCAzKSArICgxfGlkKSwgZGF0YSA9IHNsb3BlX3RpZHkgJT4lIA0KICAgICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihwb2x5ID09IDMpLCB3ZWlnaHRzID0gd2VpZ2h0X3NtcikNCiAgKQ0KDQoNCmdsb2JhbF9wcmVkaWN0aW9ucyA8LSBkYXRhLmZyYW1lKERPID0gc2VxKG1pbihzbG9wZV90aWR5JERPKSwgbWF4KHNsb3BlX3RpZHkkRE8pLCBsZW5ndGgub3V0ID0gMTAwKSkNCg0KZm9yIChtb2RlbF9uYW1lIGluIG5hbWVzKGdsb2JhbF9tb2RlbHMpKSB7DQogIHByZWRpY3Rpb25zIDwtIHByZWRpY3QoDQogICAgZ2xvYmFsX21vZGVsc1tbbW9kZWxfbmFtZV1dLCANCiAgICBuZXdkYXRhID0gZ2xvYmFsX3ByZWRpY3Rpb25zLCANCiAgICByZS5mb3JtID0gTkEsICAjIEV4Y2x1ZGVzIHJhbmRvbSBlZmZlY3RzIChwb3B1bGF0aW9uLWxldmVsIHByZWRpY3Rpb25zKQ0KICAgIHNlLmZpdCA9IFRSVUUgICMgUmV0dXJucyBzdGFuZGFyZCBlcnJvcnMNCiAgKQ0KICANCiAgZ2xvYmFsX3ByZWRpY3Rpb25zW1twYXN0ZTAobW9kZWxfbmFtZSwgIl9maXQiKV1dIDwtIHByZWRpY3Rpb25zJGZpdA0KICBnbG9iYWxfcHJlZGljdGlvbnNbW3Bhc3RlMChtb2RlbF9uYW1lLCAiX2x3ciIpXV0gPC0gcHJlZGljdGlvbnMkZml0IC0gMS45NiAqIHByZWRpY3Rpb25zJHNlLmZpdA0KICBnbG9iYWxfcHJlZGljdGlvbnNbW3Bhc3RlMChtb2RlbF9uYW1lLCAiX3VwciIpXV0gPC0gcHJlZGljdGlvbnMkZml0ICsgMS45NiAqIHByZWRpY3Rpb25zJHNlLmZpdA0KfQ0KDQpnbG9iYWxfcHJlZGljdGlvbnNfbG9uZyA8LSBnbG9iYWxfcHJlZGljdGlvbnMgJT4lDQogIHBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gbWF0Y2hlcygibG1fLipfZml0fGxtXy4qX2x3cnxsbV8uKl91cHIiKSwNCiAgICBuYW1lc190byA9IGMoIm1vZGVsIiwgIi52YWx1ZSIpLA0KICAgIG5hbWVzX3BhdHRlcm4gPSAiKGxtX1xcZCspXyguKikiDQogICkgJT4lDQogIGRwbHlyOjptdXRhdGUoYmVzdF9tb2RlbF9uYW1lID0gY2FzZV93aGVuKA0KICAgIG1vZGVsID09ICJsbV8wIiB+ICIwdGgtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgbW9kZWwgPT0gImxtXzEiIH4gIjFzdC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICBtb2RlbCA9PSAibG1fMiIgfiAiMm5kLW9yZGVyIHBvbHlub21pYWwiLA0KICAgIG1vZGVsID09ICJsbV8zIiB+ICIzcmQtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgVFJVRSB+ICJFUlJPUiINCiAgKSkNCmBgYA0KDQpGaWd1cmUgPGJyPg0KDQpgYGB7cn0NCmJlc3Rfd2VpZ2h0ZWRfbW9kZWxfcHJlZCA8LSBiZXN0X3dlaWdodGVkX21vZGVsICU+JSANCiAgZHBseXI6OmxlZnRfam9pbiguLCBtb2RlbF9wcmVkc19kZiwgYnkgPSBjKCJpZCIsICJtb2RlbCIpKSAlPiUgDQogIGRwbHlyOjp1bmdyb3VwKCkgJT4lIA0KICBkcGx5cjo6bXV0YXRlKGJlc3RfbW9kZWxfbmFtZSA9IGNhc2Vfd2hlbigNCiAgICAgIHBvbHkgPT0gMCB+ICIwdGgtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgICBwb2x5ID09IDEgfiAiMXN0LW9yZGVyIHBvbHlub21pYWwiLA0KICAgICAgcG9seSA9PSAyIH4gIjJuZC1vcmRlciBwb2x5bm9taWFsIiwNCiAgICAgIHBvbHkgPT0gMyB+ICIzcmQtb3JkZXIgcG9seW5vbWlhbCIsDQogICAgICBUUlVFIH4gIkVSUk9SIg0KICAgICkpDQoNCmFubm90YXRpb25fZGF0YSA8LSB0YWJsZV9id20gJT4lDQogIGRwbHlyOjpzZWxlY3QoYmVzdF9tb2RlbF9uYW1lLCBuKSANCg0KZmlnXzEgPC0gZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoZGF0YSA9IGJlc3Rfd2VpZ2h0ZWRfbW9kZWxfcHJlZCwgDQogICAgICAgICAgICBhZXMoeCA9IERPLCB5ID0gTU8yX3ByZWQsIGNvbG9yID0gaWQpLCBzaXplID0gMSwgYWxwaGEgPSAxKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IHNsb3BlX3RpZHksIGFlcyh4ID0gRE8sIHkgPSBNTzJfZyksIGFscGhhID0gMC4xLCBjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMikgKw0KICBnZW9tX3JpYmJvbihkYXRhID0gZ2xvYmFsX3ByZWRpY3Rpb25zX2xvbmcsIA0KICAgICAgICAgICAgICBhZXMoeCA9IERPLCB5bWluID0gbHdyLCB5bWF4ID0gdXByLCBncm91cCA9IG1vZGVsKSwgDQogICAgICAgICAgICAgIGZpbGwgPSAiI0ZDNkM4NSIsIGFscGhhID0gMC4yKSArICAjIFNoYWRlZCBjb25maWRlbmNlIGludGVydmFscw0KICBnZW9tX2xpbmUoZGF0YSA9IGdsb2JhbF9wcmVkaWN0aW9uc19sb25nLCANCiAgICAgICAgICAgIGFlcyh4ID0gRE8sIHkgPSBmaXQpLCBzaXplID0gMS41LCBjb2xvciA9ICIjRkYwMDdGIikgKw0KICBmYWNldF93cmFwKH5iZXN0X21vZGVsX25hbWUpICsNCiAgc2NhbGVfY29sb3JfZ3JleShzdGFydCA9IDAuMSwgZW5kID0gMC45KSArDQogIGxhYnMoDQogICAgICB0aXRsZSA9IHBhc3RlKCJNb2RlbCBlc3RpbWF0ZXMgYW5kIG9ic2VydmVkIGRhdGEgZ3JvdXBlZCBieSBiZXN0IGZpdHRpbmcgbW9kZWwiKSwNCiAgICAgIHggPSAiRGlzc29sdmVkIG94eWdlbiBwZXJjZW50YWdlIChETykiLA0KICAgICAgeSA9ICJNTzIgKE8yIG1nL2cvaCkiKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBnZW9tX3RleHQoZGF0YSA9IGFubm90YXRpb25fZGF0YSwgDQogICAgICAgICAgICBhZXMoeCA9IC1JbmYsIHkgPSBJbmYsIGxhYmVsID0gcGFzdGUwKCJpdGFsaWMobikgPT0gIiwgbikpLCANCiAgICAgICAgICAgIGhqdXN0ID0gLTAuMSwgdmp1c3QgPSAxLjIsIGluaGVyaXQuYWVzID0gRkFMU0UsIHBhcnNlID0gVFJVRSkNCmZpZ18xDQpgYGANCg0KDQo8YnI+DQpGb3IgdGhlIDYgZmlzaCB0aGF0IGhhZCBvMiBhbmQgTU8yIHJlbGF0aW9uc2hpcHMgYmVzdCBtb2RlbGxlZCB3aXRoIGEgbGluZWFyIGZ1bmN0aW9uLCBvbmx5IDYgYXJlIHdlcmUgcG9zaXRpdmUgcmVsYXRpb25zaGlwcyAod2hpY2ggd2Ugd291bGQgZXhwZWN0IGlmIGZpc2ggd2VyZSBveHljb25mb3JtaW5nKSwgYW5kIG5vbmUgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuIDxicj4NCg0KYGBge3J9DQppZHNfbG1fMV9saXN0IDwtIGJlc3Rfd2VpZ2h0ZWRfbW9kZWwgJT4lIA0KICBkcGx5cjo6ZmlsdGVyKG1vZGVsID09ICJsbV8xIikgJT4lIA0KICBkcGx5cjo6cHVsbChpZCkNCg0Kd2VpZ2h0ZWRfbW9kZWxfcmVzdWx0cyAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoaWQgJWluJSBpZHNfbG1fMV9saXN0ICYgbW9kZWwgPT0gImxtXzEiICYgdGVybSA9PSAiRE8iKSAlPiUgDQogIGRwbHlyOjptdXRhdGUoZXN0aW1hdGUgPSByb3VuZChlc3RpbWF0ZSwgNSksDQogICAgICAgICAgICAgICAgY2kgPSBwYXN0ZTAocm91bmQoY29uZl9sb3csNSksICIg4oCTICIsIHJvdW5kKGNvbmZfaGlnaCw1KSksDQogICAgICAgICAgICAgICAgcF92YWx1ZSA9IHJvdW5kKHBfdmFsdWUsIDMpKSAlPiUgDQogIGRwbHlyOjpzZWxlY3QoaWQsIGVzdGltYXRlLCBjaSwgcF92YWx1ZSkgJT4lIA0KICBndCgpICU+JSANCiAgY29sc19hbGlnbigNCiAgICBhbGlnbiA9ICJjZW50ZXIiLCANCiAgICBjb2x1bW5zID0gZXZlcnl0aGluZygpDQogICkNCmBgYA0KDQojIyBDYWN1bGF0aW5nIFBjcml0IHdpdGggQ2hhYm90IFNNUg0KDQpIZXJlIHdlIGJ1aWxkIHRoZSBzYW1lIG1vZGVscyBhcyBhYm92ZSBidXQgdXNpbmcgdGhlIFNNUiBlc3RpbWF0ZWQgd2l0aCB0aGUgQ2hhYm90IG1ldGhvZHMuIDxicj4NCg0KYGBge3J9DQppZHMgPC0gY2hlY2tfcGNyaXRfZGYgJT4lIA0KICBkcGx5cjo6ZGlzdGluY3QoaWQpICU+JSANCiAgZHBseXI6OnB1bGwoKQ0KDQpwY3JpdF9tb2RlbF9kZl9saXN0XzIgPC0gbGlzdCgpDQpwY3JpdF9tb2RlbHNfMiA8LSAgbGlzdCgpDQoNCmZvciAoaWRfaSBpbiBpZHMpIHsNCg0KZGZfaSA8LSBjaGVja19wY3JpdF9kZiAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkNCg0KbzJjcml0IDwtIGNhbGNPMmNyaXQoRGF0YSA9IGRmX2ksIFNNUiA9IGRmX2kkU01SX0NIQUJPVFsxXSwgbG93ZXN0TU8yPU5BLCBnYXBMaW1pdCA9IDQsDQogICAgICAgICAgICAgICAgICAgICBtYXgubmIuTU8yLmZvci5yZWcgPSA3KQ0KDQpsb3dlc3RNTzIgPSBxdWFudGlsZShkZl9pJE1PMltkZl9pJERPID49IDgwXSwgcD0wLjA1KQ0KdmF1bGUgPC0gbzJjcml0JG8yY3JpdA0KU01SIDwtIG8yY3JpdCRTTVINCm5iX21vMl9jb25mb3JtaW5nIDwtIG8yY3JpdCROYl9NTzJfY29uZm9ybWluZw0KcjIgPC0gbzJjcml0JHIyDQptZXRob2QgPC0gbzJjcml0JE1ldGhvZA0KcCA8LSBvMmNyaXQkUFsxXQ0KDQpwY3JpdF9tb2RlbF9kZiA8LSB0aWJibGUoDQogICAgICBpZCA9IGlkX2ksDQogICAgICBwY3JpdF92YXVsZSA9IHZhdWxlLA0KICAgICAgcGNyaXRfU01SID0gU01SLA0KICAgICAgcGNyaXRfbG93ZXN0TU8yID0gbG93ZXN0TU8yLA0KICAgICAgcGNyaXRfbmJfbW8yX2NvbmZvcm1pbmcgPSBuYl9tbzJfY29uZm9ybWluZywNCiAgICAgIHBjcml0X3IyID0gcjIsDQogICAgICBwY3JpdF9tZXRob2QgPSBtZXRob2QsDQogICAgICBwY3JpdF9wID0gcA0KICAgICkNCg0KcGNyaXRfbW9kZWxfZGZfbGlzdF8yW1tpZF9pXV0gPC0gcGNyaXRfbW9kZWxfZGYNCg0KcGNyaXRfbW9kZWxzXzJbW2lkX2ldXSA8LSBvMmNyaXQNCg0KfQ0KDQpwY3JpdF9tb2RlbF9kZl8yIDwtIGJpbmRfcm93cyhwY3JpdF9tb2RlbF9kZl9saXN0XzIpDQpgYGANCg0KTm93IGZpbHRlcmluZyBvdXQgYmFzZWQgb24gdGhlIHNhbWUgcnVsZXMgYWJvdmUgPGJyPg0KDQpgYGB7cn0NCnBjcml0X2xpc3RfMiA8LSBwY3JpdF9tb2RlbF9kZl8yICU+JSANCiAgZHBseXI6OmZpbHRlcihwY3JpdF9uYl9tbzJfY29uZm9ybWluZyA+IDIpICU+JSANCiAgcHVsbChpZCkNCg0KcGFzdGUwKCJCYXNlZCBvbiB0aGlzIHJ1bGUgdGhlcmUgYXJlICIsIGxlbmd0aChwY3JpdF9saXN0XzIpLCAiIGZpc2ggd2l0aCBwb3NzaWJsZSBQY3JpdHMuIikNCmBgYA0KDQpQbG90dGluZyB3aXRoIHRoZSBTTVIgQ2hhYm90IG1ldGhvZA0KDQpgYGB7cn0NCmZvciAoaWRfaSBpbiBwY3JpdF9saXN0KSB7DQogIA0KICBjb21tZW50IDwtIGNoZWNrX3Bjcml0X2RmICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6c2xpY2UoMSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUoY29tbWVudCA9IGlmX2Vsc2UoaXMubmEoY29tbWVudHMpLCAiIiwgcGFzdGUwKCIjIiwgY29tbWVudHMpKSkgJT4lIA0KICAgIHB1bGwoY29tbWVudCkNCiAgDQogIHIyIDwtIHBjcml0X21vZGVsX2RmXzIgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocGNyaXRfcjIgPSByb3VuZChwY3JpdF9yMiwgMykpICU+JSANCiAgICBkcGx5cjo6cHVsbChwY3JpdF9yMikNCiAgDQogIGNvbmZvcm1pbmcgPC0gcGNyaXRfbW9kZWxfZGZfMiAlPiUgDQogICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKSAlPiUgDQogICAgZHBseXI6Om11dGF0ZShwY3JpdF9uYl9tbzJfY29uZm9ybWluZyA9IHJvdW5kKHBjcml0X25iX21vMl9jb25mb3JtaW5nLCAzKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHBjcml0X25iX21vMl9jb25mb3JtaW5nKQ0KICANCiAgUCA8LSBwY3JpdF9tb2RlbF9kZl8yICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKGlkID09IGlkX2kpICU+JSANCiAgICBkcGx5cjo6bXV0YXRlKHBjcml0X3AgPSByb3VuZChwY3JpdF9wLCAzKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHBjcml0X3ApDQogIA0KICBTTVIgPC0gcGNyaXRfbW9kZWxfZGZfMiAlPiUgDQogICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKSAlPiUgDQogICAgZHBseXI6Om11dGF0ZShwY3JpdF9TTVIgPSByb3VuZChwY3JpdF9TTVIsIDMpKSAlPiUgDQogICAgZHBseXI6OnB1bGwocGNyaXRfU01SKQ0KICANCiAgbG93ZXN0TU8yIDwtIHBjcml0X21vZGVsX2RmXzIgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkgJT4lIA0KICAgIGRwbHlyOjptdXRhdGUocGNyaXRfbG93ZXN0TU8yID0gcm91bmQocGNyaXRfbG93ZXN0TU8yLCAzKSkgJT4lIA0KICAgIGRwbHlyOjpwdWxsKHBjcml0X2xvd2VzdE1PMikNCiAgDQogICMgR2VuZXJhdGUgYW5kIHJlbmRlciB0aGUgcGxvdA0KICBwbG90TzJjcml0KG8yY3JpdG9iaiA9IHBjcml0X21vZGVsc18yW1tpZF9pXV0pDQogIA0KICAjIEFkZCBhIHRpdGxlDQogIG10ZXh0KA0KICAgIHRleHQgPSBwYXN0ZTAoaWRfaSwgIiAiLCBjb21tZW50KSwNCiAgICBzaWRlID0gMywgbGluZSA9IDIsIGFkaiA9IDAsDQogICAgY29sID0gImJsdWUiLCBmb250ID0gMiwgY2V4ID0gMS4yDQogICkNCiAgDQogIG10ZXh0KA0KICAgIHRleHQgPSBwYXN0ZTAoIlIyID0gIiwgcjIsICI7IHAgPSAiLCBQLCAiOyBDUCA8IFNNUiA9ICIsIGNvbmZvcm1pbmcsICI7IFNNUiA9ICIsIFNNUiwgIjsgbG93ZXN0TU8yID0gIixsb3dlc3RNTzIpLA0KICAgIHNpZGUgPSAzLCBsaW5lID0gMSwgYWRqID0gMCwNCiAgICBjb2wgPSAiYmx1ZSIsIGZvbnQgPSAxLCBjZXggPSAwLjgNCiAgKQ0KfQ0KYGBgDQpgYGB7cn0NCmhhdmVfcGNpcnRfMiA8LSAgYygiYV85XzIxbm92XzMiLCAiYl8wXzI0X25vdl8xIiwgImJfMF8yNG5vdl8yIiwgImJfMF8yNW5vdl8xIiwgImJfMF8yNW5vdl8zIiwgImJfMF8yNl8xIiwgImJfOV8yMW5vdl8xIiwgImJfOV8yMW5vdl8yIiwgImJfOV8yMW5vdl8zIiwgImRfMF8yMW5vdl8zIikNCg0KbGVuZ3RoKGhhdmVfcGNpcnRfMikNCmBgYA0KDQoNCiMjIyBQY3JpdHMgbnVtYmVycw0KDQpCYXNlZCBvbiB2aXN1YWwgY2hlY2tzIHRoZSBmb2xsb3dpbmcgZmlzaCBkbyBoYXZlIGNsZWFyIFBjcml0IHZhbHVlcw0KDQpgYGB7cn0NCmRvX2hhdmVfcGNpcnRfMiA8LSAgYygiYV85XzIxbm92XzMiLCAiYl8wXzI0X25vdl8xIiwgImJfMF8yNG5vdl8yIiwgImJfMF8yNW5vdl8xIiwgImJfMF8yNW5vdl8zIiwgImJfMF8yNl8xIiwgImJfOV8yMW5vdl8xIiwgImJfOV8yMW5vdl8yIiwgImJfOV8yMW5vdl8zIiwgImRfMF8yMW5vdl8zIikNCg0Kbl9wY3JpdCA8LSBsZW5ndGgoZG9faGF2ZV9wY2lydF8yKQ0KDQpoYXZlX3BjaXJ0XzIgPC0gcGNyaXRfbW9kZWxfZGZfMiAlPiUgDQogIGRwbHlyOjpmaWx0ZXIoaWQgJWluJSBkb19oYXZlX3BjaXJ0XzIpDQoNCm1lYW5fcGNyaXQgPC0gaGF2ZV9wY2lydF8yICU+JSANCiAgZHBseXI6OnJlZnJhbWUobWVhbiA9IG1lYW4ocGNyaXRfdmF1bGUpKSAlPiUgDQogIHB1bGwobWVhbikgJT4lIA0KICByb3VuZCguLCAyKQ0KDQptaW5fcGNyaXQgPC0gaGF2ZV9wY2lydF8yICU+JSANCiAgZHBseXI6OnJlZnJhbWUobWluID0gbWluKHBjcml0X3ZhdWxlKSkgJT4lIA0KICBwdWxsKG1pbikgJT4lIA0KICByb3VuZCguLCAyKQ0KDQptYXhfcGNyaXQgPC0gaGF2ZV9wY2lydF8yICU+JSANCiAgZHBseXI6OnJlZnJhbWUobWF4ID0gbWF4KHBjcml0X3ZhdWxlKSkgJT4lIA0KICBwdWxsKG1heCkgJT4lIA0KICByb3VuZCguLCAyKQ0KDQpwcmludChwYXN0ZTAoIlRoZXJlIGFyZSAiLCBuX3Bjcml0LCAiIGZpc2ggd2l0aCBpZGVudGlmaWVkIFBjcml0cyBhbmQgdGhlIG1lYW4gUGNyaXQgaXMgIiwgbWVhbl9wY3JpdCwgIiAocmFuZ2U6ICIsIA0KICAgICAgICAgICAgIG1pbl9wY3JpdCwgIuKAkyIsIG1heF9wY3JpdCwgIikiKSkNCmBgYA0KDQojIyBPdGhlciB0ZWNobmlxdWVzIGZvciBQY3JpdCBjYWxjdWxhdGlvbg0KDQpIZXJlIHVzaW5nIHRoZSB0aGUgZmlzaCB0aGF0IHdlcmUgZGV0cmltZW50IHRvIGhhdmUgUGNyaXRzLCB3ZSB3aWxsIGFsc28gZXN0aW1hdGUgUGNyaXQgd2l0aCBmaXZlIHBvcHVsYXIgdGVjaG5pcXVlcyBmb3IgUGNyaXQgY2FsY3VsYXRpb246IHRoZSB0cmFkaXRpb25hbCBicmVha3BvaW50IG1ldHJpYyAoYnJva2VuIHN0aWNrIHJlZ3Jlc3Npb24pLCB0aGUgbm9ubGluZWFyIHJlZ3Jlc3Npb24gbWV0cmljIChNYXJzaGFsbCBldCBhbC4gMjAxMyksIHRoZSBzdWItcHJlZGljdGlvbiBpbnRlcnZhbCBtZXRyaWMgKEJpcmsgZXQgYWwuIDIwMTkpLCB0aGUgYWxwaGEtYmFzZWQgUGNyaXQgbWV0aG9kIChTZWliZWwgZXQgYWwuIDIwMjEpLCBhbmQgdGhlIGxpbmVhciBsb3cgTzIgKExMTykgbWV0aG9kIChSZWVtZXllciAmIFJlZXMgMjAxOSkuICA8YnI+DQoNClRoZSBmdW5jdGlvbiBpcyBjYWxsZWQgY2FsY19wY3JpdCgpIGFuZCBpcyBwYXJ0IG9mIHRoZSByZXNwaXJvbWV0cnkgcGFja2FnZS4gPGJyPg0KDQpMaW5rOiBodHRwczovL3NlYXJjaC5yLXByb2plY3Qub3JnL0NSQU4vcmVmbWFucy9yZXNwaXJvbWV0cnkvaHRtbC9jYWxjX3Bjcml0Lmh0bWwgPGJyPg0KDQoNCiMjIyBNb2RlbCBwYXJhbWV0ZXJzDQoNClBhcmFtZXRlcnMgdG8gY29uc2lkZXIgPGJyPg0KDQotICoqYXZnX3RvcF9uKio6IGZvciBhbHBoYSBtZXRob2QsIGEgbnVtZXJpYyB2YWx1ZSByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiB0b3AgzrEwIChNTzIvUE8yKSB2YWx1ZXMgdG8gYXZlcmFnZSB0b2dldGhlciB0byBlc3RpbWF0ZSDOsS4gRGVmYXVsdCBpcyAxLiBXZSByZWNvbW1lbmQgbm8gbW9yZSB0aGFuIDMgdG8gYXZvaWQgZGltaW5pc2hpbmcgdGhlIM6xIHZhbHVlIHdpdGggc3ViLW1heGltYWwgb2JzZXJ2YXRpb25zLiA8YnI+DQoNCi0gKipsZXZlbCoqOiBmb3IgU3ViX1BJIG1ldGhvZCwgUGVyY2VudGFnZSBhdCB3aGljaCB0aGUgcHJlZGljdGlvbiBpbnRlcnZhbCBzaG91bGQgYmUgY29uc3RydWN0ZWQuIDxicj4NCg0KLSAqKmlxcioqOiBPbmx5IGZvciBTdWJfUEkuIFJlbW92ZXMgbW8yIG9ic2VydmF0aW9ucyB0aGF0IGFyZSB0aGlzIG1hbnkgaW50ZXJxdWFydGlsZSByYW5nZXMgYXdheSBmcm9tIHRoZSBtZWFuIHZhbHVlIGZvciB0aGUgb3h5cmVndWxhdGluZyBwb3J0aW9uIG9mIHRoZSB0cmlhbC4gSWYgdGhpcyBmaWx0ZXJpbmcgaXMgbm90IGRlc2lyZWQsIHNldCB0byBpbmZpbml0eS4gIDxicj4NCg0KLSAqKk5MUl9tKio6IG9ubHkgYXBwbGllcyB0byBOTFIuIFBjcml0IGlzIGRlZmluZWQgYXMgdGhlIFBPMiBhdCB3aGljaCB0aGUgc2xvcGUgb2YgdGhlIGJlc3QgZml0dGluZyBmdW5jdGlvbiBlcXVhbHMgTkxSX20gKGFmdGVyIHRoZSBNTzIgZGF0YSBhcmUgbm9ybWFsaXplZCB0byB0aGUgOTAlIHF1YW50aWxlKS4gRGVmYXVsdCBpcyAwLjA2NSA8YnI+DQoNCi0gKipNUioqOiBBIG51bWVyaWMgdmFsdWUgZm9yIHRoZSBtZXRhYm9saWMgcmF0ZSBhdCB3aGljaCBwY3JpdF9hbHBoYSBhbmQgcGNyaXRfTExPIHNob3VsZCBiZSByZXR1cm5lZC4gSWYgbm90IHN1cHBsaWVkIGJ5IHRoZSB1c2VyLCB0aGVuIHRoZSBtZWFuIE1PMiBvZiB0aGUgIm94eXJlZ3VsYXRpbmciIHBvcnRpb24gb2YgdGhlIGN1cnZlIGlzIGFwcGxpZWQgZm9yIHBjcml0X2FscGhhIGFuZCBOQSBpcyByZXR1cm5lZCBmb3IgcGNyaXRfTExPLiA8YnI+DQoNCi0gKiptbzJfdGhyZXNob2xkKio6IEEgc2luZ2xlIG51bWVyaWMgdmFsdWUgYWJvdmUgd2hpY2ggbW8yIHZhbHVlcyBhcmUgaWdub3JlZCBmb3IgYWxwaGEgUGNyaXQgZXN0aW1hdGlvbi4gVXNlZnVsIHRvIHJlbW92aW5nIG9idmlvdXNseSBlcnJvbmVvdXMgdmFsdWVzLiBEZWZhdWx0IGlzIEluZi4NCg0KIyMjIEZvcm1hdGUgZGF0YQ0KDQpXZSB3aWxsIG9ubHkgdGhvc2UgdGhhdCBtYXkgaGF2ZSBhIFBjaXJ0LCB3ZSB3aWxsIGFsc28gbmVlZCB0byB3ZWlnaHQgdGhlIFNNUiB2YXVsZXMgc29tZSB3YXkuIEluIHRoaXMgY2FzZSB0aGVyZSBpcyBub3QgZWFzeSB3YXkgdG8gd2VpZ2h0IHRoZSBtb2RlbHMgdGhlbXNldmxlcywgc28gaW5zdGVhZCB3ZSBjYW4ganVzdCB0YWtlIGEgYXZlYWdlIG9mIHRoZSBTTVIgdmFsdWVzIGFuZCB1c2Ugb25seSB0aGF0IDxicj4NCg0KYGBge3J9DQpwY3JpdF9jaGVja19zbXJfZGYgPC0gc2xvcGVfdGlkeSAlPiUNCiAgZHBseXI6OmZpbHRlcihpZCAlaW4lIHBjcml0X2xpc3QgJiBwaGFzZSA9PSAic21yIikgJT4lIA0KICBkcGx5cjo6Z3JvdXBfYnkoaWQpICU+JSANCiAgZHBseXI6OnJlZnJhbWUoRE8gPSBtZWFuKERPLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICBNTzIgPSBTTVJbMV0sDQogICAgICAgICAgICAgICAgIFNNUiA9IFNNUlsxXSkNCg0KcGNyaXRfY2hlY2tfY2xvc2VkX2RmIDwtIHNsb3BlX3RpZHkgJT4lDQogIGRwbHlyOjpmaWx0ZXIoaWQgJWluJSBwY3JpdF9saXN0ICYgcGhhc2UgIT0gInNtciIpICU+JSANCiAgZHBseXI6OnNlbGVjdChpZCwgRE8sIE1PMiwgU01SKQ0KDQpwY3JpdF9jaGVja19kZiA8LSByYmluZChwY3JpdF9jaGVja19zbXJfZGYsIHBjcml0X2NoZWNrX2Nsb3NlZF9kZikNCg0KcGNyaXRfY2hlY2tfZGZfbiA8LSBwY3JpdF9jaGVja19kZiAlPiUNCiAgZHBseXI6OmRpc3RpbmN0KGlkKSAlPiUNCiAgbnJvdyguKQ0KDQpwYXN0ZTAoIm4gZm9yIHBvc3NpYmxlIFBjcml0ID0gIiwgcGNyaXRfY2hlY2tfZGZfbikNCmBgYA0KDQpIZXJlIHdlIGJ1aWxkIHRoZSBtb2RlbHMgPGJyPg0KDQpgYGB7cn0NCmNvbWJpbmVkX3BjaXJ0X2xpc3QgPC0gbGlzdCgpDQoNCmZvciAoaWRfaSBpbiBwY3JpdF9saXN0KSB7DQoNCiAgaWRfbmFtZSA8LSBpZF9pDQoNCiAgbW8yX2RhdGEgPC0gcGNyaXRfY2hlY2tfZGYgJT4lDQogICAgZHBseXI6OmZpbHRlcihpZCA9PSBpZF9pKQ0KDQogIE1SX3NldCA8LSBtbzJfZGF0YSRTTVJbMV0gJT4lIGFzLm51bWVyaWMoKQ0KDQogICMgVXNlIHRyeUNhdGNoIHRvIGhhbmRsZSBlcnJvcnMgYW5kIHNraXAgcHJvYmxlbWF0aWMgY2FsY3VsYXRpb25zDQogIHBjcml0X2RmIDwtIHRyeUNhdGNoKHsNCg0KICAgIHBjcml0X2RmIDwtIGNhbGNfcGNyaXQocG8yID0gbW8yX2RhdGEkRE8sDQogICAgICAgICAgIG1vMiA9IG1vMl9kYXRhJE1PMiwNCiAgICAgICAgICAgbWV0aG9kID0gJ0FsbCcsDQogICAgICAgICAgIGF2Z190b3BfbiA9IDIsICMgYWxwaGEgbWV0cmljIChkZWZhdWx0ID0gMSkgcmVjb21tZW5kIG5vIG1vcmUgdGhhbiAzDQogICAgICAgICAgIGxldmVsID0gMC45NSwgIyBTdWJfUEkgbWV0cmljIChkZWZhdWx0ID0gMC45NSkNCiAgICAgICAgICAgaXFyID0gMS41LCAjIFN1Yl9QSSBtZXRyaWMgKGRlZmF1bHQgPSAxLjUpDQogICAgICAgICAgIE5MUl9tID0gMC4wNjUsICMgTkxSIG1ldHJpYyAoZGVmYXVsdCA9IDAuMDY1KQ0KICAgICAgICAgICBNUiA9IE1SX3NldCwgIyBhbHBoYSBhbmQgTExPIG1ldHJpY3MsDQogICAgICAgICAgIG1vMl90aHJlc2hvbGQgPSBJbmYsICMgYWxwaGEgbWV0cmljDQogICAgICAgICAgIHJldHVybl9tb2RlbHMgPSBGQUxTRSAjIHJldHVybiBtb2RlbCBwYXJhbWV0ZXJzPw0KICAgICAgICAgICApICU+JQ0KICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgICAgcm93bmFtZXNfYXNfY29sdW1uKHZhciA9ICJtZXRob2QiKSAlPiUNCiAgICAgIHJlbmFtZSh2YWx1ZSA9ICIuIikgJT4lDQogICAgICB0aWR5cjo6cGl2b3Rfd2lkZXIoLiwNCiAgICAgICAgICAgICAgICAgICAgIG5hbWVzX2Zyb20gPSBtZXRob2QsDQogICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHZhbHVlKSAlPiUNCiAgICAgIGRwbHlyOjptdXRhdGUoaWQgPSBpZF9uYW1lKSAlPiUNCiAgICAgIGRwbHlyOjpzZWxlY3QoaWQsIGV2ZXJ5dGhpbmcoKSkNCg0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICBtZXNzYWdlKCJTa2lwcGluZyBjaGFubmVsICIsIGlkX25hbWUsICIgZHVlIHRvIGVycm9yOiAiLCBjb25kaXRpb25NZXNzYWdlKGUpKQ0KICAgIE5VTEwNCiAgfSkNCg0KICAjIE9ubHkgYWRkIHRvIGxpc3QgaWYgcGNyaXRfZGYgaXMgbm90IE5VTEwNCiAgaWYgKCFpcy5udWxsKHBjcml0X2RmKSkgew0KICAgIGNvbWJpbmVkX3BjaXJ0X2xpc3RbW2lkX25hbWVdXSA8LSBwY3JpdF9kZg0KICB9DQp9DQpgYGANCg0KPGJyPg0KQ29tYmluZWQgYWxsIHRoZSBQY3JpdCBtb2RlbCBlc3RpbWF0ZXMgdG9nZXRoZXIgDQoNCmBgYHtyfQ0KcGNpcnQgPC0gYmluZF9yb3dzKGNvbWJpbmVkX3BjaXJ0X2xpc3QpDQoNCmlkX3NfY29tcCA8LSBwY2lydCAlPiUgDQogIGRwbHlyOjpwdWxsKCkNCmBgYA0KDQojIyMgUGxvdHRpbmcgUGNyaXQNCg0KSGVyZSB3ZSB3aWxsIHNhdmUgdGhlIHBsb3RzIGZvciB0aGUgdmFyaW91cyBQY3JpdCBjdXJ2ZXMuDQoNCmBgYHtyfQ0KIyBDcmVhdGUgb3V0cHV0IGRpcmVjdG9yeSBpZiBuZWVkZWQNCm91dHB1dF9maWdfcGNyaXRfYWx0ZXJuYXRpdmVfd2QgPC0gZmlsZS5wYXRoKG91dHB1dF9maWdfd2QsICJwY3JpdC1hbHRlcm5hdGl2ZSIpDQppZiAoIWRpci5leGlzdHMob3V0cHV0X2ZpZ19wY3JpdF9hbHRlcm5hdGl2ZV93ZCkpIHsNCiAgZGlyLmNyZWF0ZShvdXRwdXRfZmlnX3Bjcml0X2FsdGVybmF0aXZlX3dkKQ0KfQ0KDQojIE9wZW4gYSBzaW5nbGUgUERGIGRldmljZSBvbmNlDQpwZGYoZmlsZSA9IGZpbGUucGF0aChvdXRwdXRfZmlnX3Bjcml0X2FsdGVybmF0aXZlX3dkLCAiY29tYmluZWRfcGNyaXRfcGxvdHMucGRmIiksDQogICAgd2lkdGggPSA4LCBoZWlnaHQgPSA2KQ0KDQpmb3IgKGlkX2kgaW4gcGNyaXRfbGlzdCkgew0KDQogIGlkX25hbWUgPC0gaWRfaQ0KDQogIG1vMl9kYXRhIDwtIHBjcml0X2NoZWNrX2RmICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkNCg0KICBNUl9zZXQgPC0gbW8yX2RhdGEkU01SWzFdICU+JSBhcy5udW1lcmljKCkNCg0KICB0cnlDYXRjaCh7DQogICAgIyBHZW5lcmF0ZSBhbmQgcmVuZGVyIHRoZSBwbG90DQogICAgcGxvdF9wY3JpdCgNCiAgICAgIHBvMiA9IG1vMl9kYXRhJERPLA0KICAgICAgbW8yID0gbW8yX2RhdGEkTU8yLA0KICAgICAgbWV0aG9kID0gJ0FsbCcsDQogICAgICBhdmdfdG9wX24gPSAxLA0KICAgICAgbGV2ZWwgPSAwLjk1LA0KICAgICAgaXFyID0gMS41LA0KICAgICAgTkxSX20gPSAwLjA2NSwNCiAgICAgIE1SID0gTVJfc2V0LA0KICAgICAgbW8yX3RocmVzaG9sZCA9IEluZiwNCiAgICAgIHJldHVybl9tb2RlbHMgPSBGQUxTRSwNCiAgICAgIHNob3dOTFJzID0gRkFMU0UNCiAgICApDQoNCiAgICAjIEFkZCBhIHRpdGxlIGluIHRoZSB0b3AtbGVmdCBjb3JuZXINCiAgICBtdGV4dCh0ZXh0ID0gcGFzdGUoaWRfbmFtZSksDQogICAgICAgICAgc2lkZSA9IDMsIGxpbmUgPSAyLCBhZGogPSAwLCAjIFRvcCBtYXJnaW4sIGFsaWduZWQgdG8gbGVmdA0KICAgICAgICAgIGNvbCA9ICJibHVlIiwgZm9udCA9IDIsIGNleCA9IDEuMikNCg0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICBtZXNzYWdlKCJTa2lwcGluZyBjaGFubmVsICIsIGlkX25hbWUsICIgZHVlIHRvIGVycm9yOiAiLCBjb25kaXRpb25NZXNzYWdlKGUpKQ0KICB9KQ0KfQ0KDQojIENsb3NlIHRoZSBQREYgZGV2aWNlICphZnRlciogdGhlIGxvb3ANCmRldi5vZmYoKQ0KYGBgDQoNCjxicj4NClBsb3R0aW5nIGluIHRoZSBodG1sLiBOb25lIG9mIHRoZSBtb2RlbHMgYXBwZWFyIHRvIGVzdGltYXRlIGEgUGNyaXQgdmFsdWUgY29udmluY2luZ2x5LiANCg0KYGBge3J9DQpmb3IgKGlkX2kgaW4gcGNyaXRfbGlzdCkgew0KDQogIGlkX25hbWUgPC0gaWRfaQ0KDQogIG1vMl9kYXRhIDwtIHBjcml0X2NoZWNrX2RmICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoaWQgPT0gaWRfaSkNCg0KICBNUl9zZXQgPC0gbW8yX2RhdGEkU01SWzFdICU+JSBhcy5udW1lcmljKCkNCg0KICB0cnlDYXRjaCh7DQogICAgIyBHZW5lcmF0ZSBhbmQgcmVuZGVyIHRoZSBwbG90DQogICAgcGxvdF9wY3JpdCgNCiAgICAgIHBvMiA9IG1vMl9kYXRhJERPLA0KICAgICAgbW8yID0gbW8yX2RhdGEkTU8yLA0KICAgICAgbWV0aG9kID0gJ0FsbCcsDQogICAgICBhdmdfdG9wX24gPSAxLA0KICAgICAgbGV2ZWwgPSAwLjk1LA0KICAgICAgaXFyID0gMS41LA0KICAgICAgTkxSX20gPSAwLjA2NSwNCiAgICAgIE1SID0gTVJfc2V0LA0KICAgICAgbW8yX3RocmVzaG9sZCA9IEluZiwNCiAgICAgIHJldHVybl9tb2RlbHMgPSBGQUxTRSwNCiAgICAgIHNob3dOTFJzID0gRkFMU0UNCiAgICApDQoNCiAgICAjIEFkZCBhIHRpdGxlIGluIHRoZSB0b3AtbGVmdCBjb3JuZXINCiAgICBtdGV4dCh0ZXh0ID0gcGFzdGUoaWRfbmFtZSksDQogICAgICAgICAgc2lkZSA9IDMsIGxpbmUgPSAyLCBhZGogPSAwLCAjIFRvcCBtYXJnaW4sIGFsaWduZWQgdG8gbGVmdA0KICAgICAgICAgIGNvbCA9ICJibHVlIiwgZm9udCA9IDIsIGNleCA9IDEuMikNCg0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICBtZXNzYWdlKCJTa2lwcGluZyBjaGFubmVsICIsIGlkX25hbWUsICIgZHVlIHRvIGVycm9yOiAiLCBjb25kaXRpb25NZXNzYWdlKGUpKQ0KICB9KQ0KfQ0KYGBgDQo=